shlomiassaf / ngc-webpack

Angular compiler-cli with webpack's loader chain.
MIT License
84 stars 15 forks source link

Loader chain doesn't run in `require` and results in `Expected 'styles' to be an array of strings` #16

Closed bisubus closed 7 years ago

bisubus commented 7 years ago

Loader chain itself works fine, and const style = require('./foo-bar.scss') results in a string, the problem looks like compilation precedes loader chain when require is used.

Having styleUrls for component styles works well with ngc-webpack:

@Component({
  selector: 'foo-bar',
  styleUrls: './foo-bar.scss',
   ...

But it cannot be easily used in JIT.

And having require for component styles like

@Component({
  selector: 'foo-bar',
  styles: [require('./foo-bar.scss')],
   ...

results in Webpack error:

Angular compilation done, starting webpack bundling.
Error: Expected 'styles' to be an array of strings.
    at assertArrayOfStrings (<project-root>\node_modules\@angular\compiler\@angular\compiler.es5.js:3356:12)
    at CompileMetadataResolver.getNonNormalizedDirectiveMetadata (<project-root>\node_modules\@angular\compiler\bundles\compiler.umd.js:13785:13)
    at CompileMetadataResolver._loadDirectiveMetadata (<project-root>\node_modules\@angular\compiler\@angular\compiler.es5.js:13698:17)
    at <project-root>\node_modules\@angular\compiler\@angular\compiler.es5.js:13922:54
    ...

Debugging assertArrayOfStrings showed that styles: [require('./foo-bar.scss')] is evaluated in compiler to styles: [null], no matter how style loaders are configured. It can be styles: [require('raw-loader!foo-bar.html')] or styles: [require('non-existing-loader!non-existing-file')], the result is the same. Loaders just aren't considered at the moment when compiler runs.

The setup is loosely based on https://github.com/AngularClass/angular2-webpack-starter and is quite ordinary, here are the most relevant details:

package.json

  ...
  "devDependencies": {
    "@angular/compiler-cli": "^4.0.2",
    "ngc-webpack": "^2.0.0",
    "awesome-typescript-loader": "^3.0.0",
    "typescript": "~2.2.0",
    "webpack": "~2.4.1",
    "css-loader": "^0.25.0",
    "node-sass": "^4.5.2",
    "raw-loader": "^0.5.1",
    "sass-loader": "^6.0.3",
    "to-string-loader": "^1.1.4",
    ...
  },
  ...

webpack.config.js

module.exports = {
    entry: {
        'polyfills': './src/polyfills.ts',
        'main': process.env.AOT
            ? './src/main.aot.ts'
            : './src/main.jit.ts'
    },

    resolve: {
        extensions: ['.ts', '.js', '.json'],
        modules: [
            path.join(ROOT, 'src'),
            NODE_MODULES
        ]
    },

    module: {
        loaders: [
            {
                test: /\.ts$/,
                loaders: [
                    {
                        loader: 'awesome-typescript-loader',
                        options: { configFileName: 'tsconfig.json' }
                    }
                ],
                exclude: [/\.(spec|e2e)\.ts$/]
            },
            {
                test: /\.json$/,
                loader: 'json-loader'
            },
            {
                test: /\.css$/,
                loaders: ['to-string-loader', 'css-loader']
            },
            {
                test: /\.scss$/,
                loader: [
                    'raw-loader',
                    {
                        loader: 'sass-loader',
                        options: { includePaths: [NODE_MODULES }
                    }
                ]
            },
            {
                test: /\.html$/,
                loader: 'html-loader',
                exclude: []
            }
        ]
    },

    plugins: [
        new CommonsChunkPlugin({ name: ['polyfills'] }),
        new CheckerPlugin,
        new ngcWebpack.NgcWebpackPlugin({
            disabled: !process.env.AOT,
            tsConfig: path.join(ROOT, 'tsconfig.json'),
            resourceOverride: path.join(ROOT_PATH, 'aot-empty-resource.js')
        })
    ],

    node: {
        global: true,
        crypto: 'empty',
        process: true,
        module: false,
        clearImmediate: false,
        setImmediate: false
    }
}

tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "alwaysStrict": true,
    "sourceMap": true,
    "noEmitHelpers": true,
    "importHelpers": true,
    "strictNullChecks": false,
    "baseUrl": "./src",
    "paths": [],
    "lib": [
      "dom",
      "es5"
    ],
    "types": [...],
    "typeRoots": ["./node_modules/@types"]
  },
  "exclude": [
    "node_modules",
    "dist"
  ],
  "compileOnSave": false,
  "buildOnSave": false
}
shlomiassaf commented 7 years ago

@bisubus Hi

Sorry for the late response.

This is not an ngc-webpack issue, it is just how the AOT compiler works.

The AOT compiler does not know about webpack and it expects client code, sending require to it will make it fail.

ngc-webpack does not process your code, the AOT compiler does (actually it's TS) and both AOT compiler and TS doesn't know about require

But it cannot be easily used in JIT.

This is not correct, use angular2-template-loader as done in angular-starter This will work in JIT and AOT.

Use it and it will solve the issue, this is exactly the purpose of that loader.

bisubus commented 7 years ago

@shlomiassaf Thank you for commenting on this. Are you certain about angular2-template-loader and the way it works with ngc-webpack? I have it in my config currently, but I honestly don't remember how it affected AOT/JiT build.

IIUC, all angular2-template-loader is supposed to do is transforming styleUrls: ['./foo-bar.scss'] to styles: [require('./foo-bar.scss')], isn't it? Why is it expected to work then, while using styles: [require('./foo-bar.scss')] directly doesn't work?

shlomiassaf commented 7 years ago

You forget that AOT build process has 2 steps.

First step is the AOT compiler going over TS files any creating ngfactory files from them, this is pure TS code generation and does not involve webpack and exactly why require does not work. It's also why you need to write only a string literal for the path.

The template loader converts that string path into a require statement and this happens in the 2nd step, where webpack bundles everything.

Funny thing but the template loader is only for JIT. In AOT all of the data is already in the metadata ngfactory files that the AOT compiler creates

Sent from my iPhone

On Jul 31, 2017, at 12:36 AM, bisubus notifications@github.com wrote:

@shlomiassaf Thank you for commenting on this. Are you certain about angular2-template-loader and the way it works with ngc-webpack? I have it in my config currently, but I honestly don't remember how it affected AOT/JiT build.

IIUC, all angular2-template-loader is supposed to do is transforming styleUrls: ['./foo-bar.scss'] to styles: [require('./foo-bar.scss')], isn't it? Why is it expected to work then, while using styles: [require('./foo-bar.scss')] directly doesn't work?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

bisubus commented 7 years ago

@shlomiassaf The explanation makes sense, thanks. I hope the issue is solved for me.