From 099d21b3aa17f2e304c3e988a40d5850042fd7da Mon Sep 17 00:00:00 2001 From: Jimit Ndiaye Date: Thu, 13 Oct 2016 11:08:08 +0100 Subject: [PATCH 1/7] Add extract-text-webpack-plugin dependency --- package.json | 1 + packages/angular-cli/package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/package.json b/package.json index 155aae534fb8..7c6e21cb9d91 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "exit": "^0.1.2", "exports-loader": "^0.6.3", "expose-loader": "^0.7.1", + "extract-text-webpack-plugin": "^2.0.0-beta.4", "file-loader": "^0.8.5", "fs-extra": "^0.30.0", "fs.realpath": "^1.0.0", diff --git a/packages/angular-cli/package.json b/packages/angular-cli/package.json index 39392bdbcbef..d4f5dbd6e25c 100644 --- a/packages/angular-cli/package.json +++ b/packages/angular-cli/package.json @@ -49,6 +49,7 @@ "exit": "^0.1.2", "exports-loader": "^0.6.3", "expose-loader": "^0.7.1", + "extract-text-webpack-plugin": "^2.0.0-beta.4", "file-loader": "^0.8.5", "fs-extra": "^0.30.0", "fs.realpath": "^1.0.0", From ca966bfface12184893fd3defa5d4d1c0a7d35c5 Mon Sep 17 00:00:00 2001 From: Jimit Ndiaye Date: Thu, 13 Oct 2016 12:09:23 +0100 Subject: [PATCH 2/7] Use extract-text-plugin to extract css bundle for production builds. Development builds continue to use style-loader for fast developer workflow (HMR). Production builds use static, minimized, CSS file. --- .../models/webpack-build-common.ts | 18 ------------ .../models/webpack-build-development.ts | 27 +++++++++++++++++ .../models/webpack-build-production.ts | 29 +++++++++++++++++++ 3 files changed, 56 insertions(+), 18 deletions(-) diff --git a/packages/angular-cli/models/webpack-build-common.ts b/packages/angular-cli/models/webpack-build-common.ts index aae14af6663c..c00ac1fec722 100644 --- a/packages/angular-cli/models/webpack-build-common.ts +++ b/packages/angular-cli/models/webpack-build-common.ts @@ -71,24 +71,6 @@ export function getWebpackCommonConfig( loaders: ['raw-loader', 'postcss-loader', 'sass-loader'] }, - // outside of main, load it via style-loader -        { - include: styles, - test: /\.css$/, - loaders: ['style-loader', 'css-loader', 'postcss-loader'] - }, { - include: styles, - test: /\.styl$/, - loaders: ['style-loader', 'css-loader', 'postcss-loader', 'stylus-loader'] - }, { - include: styles, - test: /\.less$/, - loaders: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader'] - }, { - include: styles, - test: /\.scss$|\.sass$/, - loaders: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'] - }, // load global scripts using script-loader { include: scripts, test: /\.js$/, loader: 'script-loader' }, diff --git a/packages/angular-cli/models/webpack-build-development.ts b/packages/angular-cli/models/webpack-build-development.ts index ed5305df709f..2387f831fdda 100644 --- a/packages/angular-cli/models/webpack-build-development.ts +++ b/packages/angular-cli/models/webpack-build-development.ts @@ -1,6 +1,11 @@ const path = require('path'); export const getWebpackDevConfigPartial = function(projectRoot: string, appConfig: any) { + const appRoot = path.resolve(projectRoot, appConfig.root); + const styles = appConfig.styles + ? appConfig.styles.map((style: string) => path.resolve(appRoot, style)) + : []; + const cssLoaders = ['style-loader', 'css-loader?sourcemap', 'postcss-loader']; return { devtool: 'cheap-module-source-map', output: { @@ -8,6 +13,28 @@ export const getWebpackDevConfigPartial = function(projectRoot: string, appConfi filename: '[name].bundle.js', sourceMapFilename: '[name].map', chunkFilename: '[id].chunk.js' + }, + module: { + rules: [ + // outside of main, load it via style-loader for development builds +        { + include: styles, + test: /\.css$/, + loaders: cssLoaders + }, { + include: styles, + test: /\.styl$/, + loaders: [...cssLoaders, 'stylus-loader?sourcemap'] + }, { + include: styles, + test: /\.less$/, + loaders: [...cssLoaders, 'less-loader?sourcemap'] + }, { + include: styles, + test: /\.scss$|\.sass$/, + loaders: [...cssLoaders, 'sass-loader?sourcemap'] + }, + ] } }; }; diff --git a/packages/angular-cli/models/webpack-build-production.ts b/packages/angular-cli/models/webpack-build-production.ts index 3ff408ad9a07..23a015d3c7a2 100644 --- a/packages/angular-cli/models/webpack-build-production.ts +++ b/packages/angular-cli/models/webpack-build-production.ts @@ -2,6 +2,7 @@ import * as path from 'path'; const WebpackMd5Hash = require('webpack-md5-hash'); const CompressionPlugin = require('compression-webpack-plugin'); import * as webpack from 'webpack'; +const ExtractTextPlugin = require('extract-text-webpack-plugin'); declare module 'webpack' { export interface LoaderOptionsPlugin {} @@ -14,6 +15,11 @@ declare module 'webpack' { } export const getWebpackProdConfigPartial = function(projectRoot: string, appConfig: any) { + const appRoot = path.resolve(projectRoot, appConfig.root); + const styles = appConfig.styles + ? appConfig.styles.map((style: string) => path.resolve(appRoot, style)) + : []; + const cssLoaders = ['css-loader?sourcemap&minimize', 'postcss-loader']; return { devtool: 'source-map', output: { @@ -22,7 +28,30 @@ export const getWebpackProdConfigPartial = function(projectRoot: string, appConf sourceMapFilename: '[name].[chunkhash].bundle.map', chunkFilename: '[id].[chunkhash].chunk.js' }, + module: { + rules: [ + // outside of main, load it via extract-text-plugin for production builds +        { + include: styles, + test: /\.css$/, + loaders: ExtractTextPlugin.extract(cssLoaders) + }, { + include: styles, + test: /\.styl$/, + loaders: ExtractTextPlugin.extract([...cssLoaders, 'stylus-loader?sourcemap']) + }, { + include: styles, + test: /\.less$/, + loaders: ExtractTextPlugin.extract([...cssLoaders, 'less-loader?sourcemap']) + }, { + include: styles, + test: /\.scss$|\.sass$/, + loaders: ExtractTextPlugin.extract([...cssLoaders, 'sass-loader?sourcemap']) + }, + ] + }, plugins: [ + new ExtractTextPlugin('[name].[contenthash].bundle.css'), new WebpackMd5Hash(), new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') From ec26e198d560027caaf3f46955cc35d12ebdebfa Mon Sep 17 00:00:00 2001 From: Jimit Ndiaye Date: Tue, 18 Oct 2016 13:15:39 +0100 Subject: [PATCH 3/7] Added e2e tests for prod build covering style bundling for css, less and sass --- tests/e2e/tests/build/prod-build.ts | 1 + tests/e2e/tests/build/styles/css.ts | 46 +++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/tests/e2e/tests/build/prod-build.ts b/tests/e2e/tests/build/prod-build.ts index fe0699af4356..0e9074a24dc0 100644 --- a/tests/e2e/tests/build/prod-build.ts +++ b/tests/e2e/tests/build/prod-build.ts @@ -34,6 +34,7 @@ export default function() { .then(() => expectFileToExist(join(process.cwd(), 'dist'))) // Check for cache busting hash script src .then(() => expectFileToMatch('dist/index.html', /main\.[0-9a-f]{20}\.bundle\.js/)) + .then(() => expectFileToMatch('dist/index.html', /styles\.[0-9a-f]{20}\.bundle\.css/)) // Check that the process didn't change local files. .then(() => expectGitToBeClean()) diff --git a/tests/e2e/tests/build/styles/css.ts b/tests/e2e/tests/build/styles/css.ts index aa081ef1f45c..e2d817020895 100644 --- a/tests/e2e/tests/build/styles/css.ts +++ b/tests/e2e/tests/build/styles/css.ts @@ -1,19 +1,61 @@ +import * as fs from 'fs'; + import {writeMultipleFiles, expectFileToMatch} from '../../../utils/fs'; import {ng} from '../../../utils/process'; +import {updateJsonFile} from '../../../utils/project'; export default function() { return writeMultipleFiles({ 'src/styles.css': ` @import './imported-styles.css'; - + body { background-color: blue; } `, 'src/imported-styles.css': ` p { background-color: red; } + `, + 'src/styles.less': ` + .outer { + .inner { + background: #fff; + } + } + `, + 'src/styles.scss': ` + .upper { + .lower { + background: #def; + } + } ` }) + .then(() => updateJsonFile('angular-cli.json', configJson => { + const app = configJson['apps'][0]; + app['styles'].push('src/styles.less'); + app['styles'].push('src/styles.scss'); + })) .then(() => ng('build')) .then(() => expectFileToMatch('dist/styles.bundle.js', 'body { background-color: blue; }')) - .then(() => expectFileToMatch('dist/styles.bundle.js', 'p { background-color: red; }')); + .then(() => expectFileToMatch('dist/styles.bundle.js', 'p { background-color: red; }')) + .then(() => expectFileToMatch('dist/styles.bundle.js', /.outer.*.inner.*background:\s*#[fF]+/)) + .then(() => expectFileToMatch('dist/styles.bundle.js', /.upper.*.lower.*background.*#def/)) + + .then(() => ng('build', '--prod')) + .then(() => new Promise((resolve, reject) => { + fs.readdir('dist', (err, files) => { + if (err) { + reject(err); + } else { + const styles = files.find(file => /styles\.[0-9a-f]{20}\.bundle\.css/.test(file)); + resolve(styles); + } + }); + })) + .then((styles) => + expectFileToMatch(styles, 'body { background-color: blue; }') + .then(() => expectFileToMatch(styles, 'p { background-color: red; }') + .then(() => expectFileToMatch(styles, /.outer.*.inner.*background:\s*#[fF]+/)) + .then(() => expectFileToMatch(styles, /.upper.*.lower.*background.*#def/))) + ); } From 78f8610c7416181f612ee4c1edfd8c365272166b Mon Sep 17 00:00:00 2001 From: Jimit Ndiaye Date: Tue, 18 Oct 2016 15:15:38 +0100 Subject: [PATCH 4/7] Update production build e2e test to expect 32 character content hash in style bundle file name --- tests/e2e/tests/build/prod-build.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/tests/build/prod-build.ts b/tests/e2e/tests/build/prod-build.ts index 0e9074a24dc0..f4d798b70485 100644 --- a/tests/e2e/tests/build/prod-build.ts +++ b/tests/e2e/tests/build/prod-build.ts @@ -34,7 +34,7 @@ export default function() { .then(() => expectFileToExist(join(process.cwd(), 'dist'))) // Check for cache busting hash script src .then(() => expectFileToMatch('dist/index.html', /main\.[0-9a-f]{20}\.bundle\.js/)) - .then(() => expectFileToMatch('dist/index.html', /styles\.[0-9a-f]{20}\.bundle\.css/)) + .then(() => expectFileToMatch('dist/index.html', /styles\.[0-9a-f]{32}\.bundle\.css/)) // Check that the process didn't change local files. .then(() => expectGitToBeClean()) From 9bec7c92bbc84081afb4083ca504e1b4f41b6f41 Mon Sep 17 00:00:00 2001 From: Jimit Ndiaye Date: Tue, 18 Oct 2016 15:45:34 +0100 Subject: [PATCH 5/7] fix paths for styles.less and styles.scss in css e2e test --- tests/e2e/tests/build/styles/css.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/tests/build/styles/css.ts b/tests/e2e/tests/build/styles/css.ts index e2d817020895..073c35d183f4 100644 --- a/tests/e2e/tests/build/styles/css.ts +++ b/tests/e2e/tests/build/styles/css.ts @@ -32,8 +32,8 @@ export default function() { }) .then(() => updateJsonFile('angular-cli.json', configJson => { const app = configJson['apps'][0]; - app['styles'].push('src/styles.less'); - app['styles'].push('src/styles.scss'); + app['styles'].push('styles.less'); + app['styles'].push('styles.scss'); })) .then(() => ng('build')) .then(() => expectFileToMatch('dist/styles.bundle.js', 'body { background-color: blue; }')) From b418afdd896ca19440abac2ad6932801a8337811 Mon Sep 17 00:00:00 2001 From: Jimit Ndiaye Date: Tue, 18 Oct 2016 16:10:37 +0100 Subject: [PATCH 6/7] use glob to find path to hashed styles bundle for prod build --- tests/e2e/tests/build/styles/css.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/tests/e2e/tests/build/styles/css.ts b/tests/e2e/tests/build/styles/css.ts index 073c35d183f4..92a2fe4915ed 100644 --- a/tests/e2e/tests/build/styles/css.ts +++ b/tests/e2e/tests/build/styles/css.ts @@ -1,4 +1,5 @@ import * as fs from 'fs'; +import * as glob from 'glob'; import {writeMultipleFiles, expectFileToMatch} from '../../../utils/fs'; import {ng} from '../../../utils/process'; @@ -42,16 +43,8 @@ export default function() { .then(() => expectFileToMatch('dist/styles.bundle.js', /.upper.*.lower.*background.*#def/)) .then(() => ng('build', '--prod')) - .then(() => new Promise((resolve, reject) => { - fs.readdir('dist', (err, files) => { - if (err) { - reject(err); - } else { - const styles = files.find(file => /styles\.[0-9a-f]{20}\.bundle\.css/.test(file)); - resolve(styles); - } - }); - })) + .then(() => new Promise(() => + glob.sync('dist/styles.*.bundle.css').find(file => !!file))) .then((styles) => expectFileToMatch(styles, 'body { background-color: blue; }') .then(() => expectFileToMatch(styles, 'p { background-color: red; }') From 0a6b40db24857820524905e8fd6c80e44c6a0038 Mon Sep 17 00:00:00 2001 From: Jimit Ndiaye Date: Tue, 18 Oct 2016 16:22:23 +0100 Subject: [PATCH 7/7] chore(style): remove unused fs import --- tests/e2e/tests/build/styles/css.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/e2e/tests/build/styles/css.ts b/tests/e2e/tests/build/styles/css.ts index 92a2fe4915ed..7fbe8ed8982f 100644 --- a/tests/e2e/tests/build/styles/css.ts +++ b/tests/e2e/tests/build/styles/css.ts @@ -1,4 +1,3 @@ -import * as fs from 'fs'; import * as glob from 'glob'; import {writeMultipleFiles, expectFileToMatch} from '../../../utils/fs';