Skip to content

Commit

Permalink
feat(build): add lazy styles/scripts (angular#3402)
Browse files Browse the repository at this point in the history
  • Loading branch information
filipesilva authored and MRHarrison committed Feb 9, 2017
1 parent 60dd64d commit e04a620
Show file tree
Hide file tree
Showing 20 changed files with 532 additions and 255 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
"resolve": "^1.1.7",
"rimraf": "^2.5.3",
"rsvp": "^3.0.17",
"sass-loader": "^3.2.0",
"sass-loader": "^4.0.1",
"script-loader": "^0.7.0",
"semver": "^5.1.0",
"silent-error": "^1.0.0",
Expand Down
46 changes: 39 additions & 7 deletions packages/angular-cli/lib/config/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,17 @@
"default": "dist/"
},
"assets": {
"fixme": true,
"type": "array",
"items": {
"type": "string"
},
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
}
],
"default": []
},
"index": {
Expand All @@ -62,15 +68,41 @@
"description": "Global styles to be included in the build.",
"type": "array",
"items": {
"type": "string"
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"input": {
"type": "string"
}
},
"additionalProperties": true
}
]
},
"additionalProperties": false
},
"scripts": {
"description": "Global scripts to be included in the build.",
"type": "array",
"items": {
"type": "string"
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"input": {
"type": "string"
}
},
"additionalProperties": true
}
]
},
"additionalProperties": false
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ function _parseJsonPath(path: string): string[] {
function _getSchemaNodeForPath<T>(rootMetaData: SchemaTreeNode<T>,
path: string): SchemaTreeNode<any> {
let fragments = _parseJsonPath(path);
// TODO: make this work with union (oneOf) schemas
return fragments.reduce((md: SchemaTreeNode<any>, current: string) => {
return md && md.children && md.children[current];
}, rootMetaData);
Expand Down
17 changes: 13 additions & 4 deletions packages/angular-cli/models/json-schema/schema-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,21 @@ export abstract class NonLeafSchemaTreeNode<T> extends SchemaTreeNode<T> {
protected _createChildProperty<T>(name: string, value: T, forward: SchemaTreeNode<T>,
schema: Schema, define = true): SchemaTreeNode<T> {

// TODO: fix this
if (schema['fixme'] && typeof value === 'string') {
value = <T>(<any>[ value ]);
let type: string;

if (!schema['oneOf']) {
type = schema['type'];
} else {
for (let testSchema of schema['oneOf']) {
if ((testSchema['type'] === 'array' && Array.isArray(value))
|| typeof value === testSchema['type']) {
type = testSchema['type'];
schema = testSchema;
break;
}
}
}

const type = schema['type'];
let Klass: any = null;

switch (type) {
Expand Down
146 changes: 76 additions & 70 deletions packages/angular-cli/models/webpack-build-common.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import * as webpack from 'webpack';
import * as path from 'path';
import {GlobCopyWebpackPlugin} from '../plugins/glob-copy-webpack-plugin';
import {packageChunkSort} from '../utilities/package-chunk-sort';
import {BaseHrefWebpackPlugin} from '@angular-cli/base-href-webpack';
import { GlobCopyWebpackPlugin } from '../plugins/glob-copy-webpack-plugin';
import { SuppressEntryChunksWebpackPlugin } from '../plugins/suppress-entry-chunks-webpack-plugin';
import { packageChunkSort } from '../utilities/package-chunk-sort';
import { BaseHrefWebpackPlugin } from '@angular-cli/base-href-webpack';
import { extraEntryParser, makeCssLoaders } from './webpack-build-utils';

const ProgressPlugin = require('webpack/lib/ProgressPlugin');
const autoprefixer = require('autoprefixer');
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const SilentError = require('silent-error');

Expand All @@ -14,21 +17,12 @@ const SilentError = require('silent-error');
*
* require('source-map-loader')
* require('raw-loader')
* require('postcss-loader')
* require('stylus-loader')
* require('less-loader')
* require('sass-loader')
* require('script-loader')
* require('json-loader')
* require('url-loader')
* require('file-loader')
*
* require('node-sass')
* require('less')
* require('stylus')
*/


export function getWebpackCommonConfig(
projectRoot: string,
environment: string,
Expand All @@ -43,25 +37,64 @@ export function getWebpackCommonConfig(
const appRoot = path.resolve(projectRoot, appConfig.root);
const appMain = path.resolve(appRoot, appConfig.main);
const nodeModules = path.resolve(projectRoot, 'node_modules');
const styles = appConfig.styles
? appConfig.styles.map((style: string) => path.resolve(appRoot, style))
: [];
const scripts = appConfig.scripts
? appConfig.scripts.map((script: string) => path.resolve(appRoot, script))
: [];
const extraPlugins: any[] = [];

let entry: { [key: string]: string[] } = {

let extraPlugins: any[] = [];
let extraRules: any[] = [];
let lazyChunks: string[] = [];

let entryPoints: { [key: string]: string[] } = {
main: [appMain]
};

if (!(environment in appConfig.environments)) {
throw new SilentError(`Environment "${environment}" does not exist.`);
}

// Only add styles/scripts if there's actually entries there
if (appConfig.styles.length > 0) { entry['styles'] = styles; }
if (appConfig.scripts.length > 0) { entry['scripts'] = scripts; }
// process global scripts
if (appConfig.scripts.length > 0) {
const globalScrips = extraEntryParser(appConfig.scripts, appRoot, 'scripts');

// add entry points and lazy chunks
globalScrips.forEach(script => {
if (script.lazy) { lazyChunks.push(script.entry); }
entryPoints[script.entry] = (entryPoints[script.entry] || []).concat(script.path);
});

// load global scripts using script-loader
extraRules.push({
include: globalScrips.map((script) => script.path), test: /\.js$/, loader: 'script-loader'
});
}

// process global styles
if (appConfig.styles.length === 0) {
// create css loaders for component css
extraRules.push(...makeCssLoaders());
} else {
const globalStyles = extraEntryParser(appConfig.styles, appRoot, 'styles');
let extractedCssEntryPoints: string[] = [];
// add entry points and lazy chunks
globalStyles.forEach(style => {
if (style.lazy) { lazyChunks.push(style.entry); }
if (!entryPoints[style.entry]) {
// since this entry point doesn't exist yet, it's going to only have
// extracted css and we can supress the entry point
extractedCssEntryPoints.push(style.entry);
entryPoints[style.entry] = (entryPoints[style.entry] || []).concat(style.path);
} else {
// existing entry point, just push the css in
entryPoints[style.entry].push(style.path);
}
});

// create css loaders for component css and for global css
extraRules.push(...makeCssLoaders(globalStyles.map((style) => style.path)));

if (extractedCssEntryPoints.length > 0) {
// don't emit the .js entry point for extracted styles
extraPlugins.push(new SuppressEntryChunksWebpackPlugin({ chunks: extractedCssEntryPoints }));
}
}

if (vendorChunk) {
extraPlugins.push(new webpack.optimize.CommonsChunkPlugin({
Expand All @@ -71,12 +104,7 @@ export function getWebpackCommonConfig(
}));
}

if (progress) {
extraPlugins.push(new ProgressPlugin({
profile: verbose,
colors: true
}));
}
if (progress) { extraPlugins.push(new ProgressPlugin({ profile: verbose, colors: true })); }

return {
devtool: sourcemap ? 'source-map' : false,
Expand All @@ -85,10 +113,10 @@ export function getWebpackCommonConfig(
modules: [nodeModules],
},
resolveLoader: {
modules: [path.resolve(projectRoot, 'node_modules')]
modules: [nodeModules]
},
context: projectRoot,
entry: entry,
entry: entryPoints,
output: {
path: path.resolve(projectRoot, appConfig.outDir),
filename: '[name].bundle.js',
Expand All @@ -97,48 +125,22 @@ export function getWebpackCommonConfig(
},
module: {
rules: [
{
enforce: 'pre',
test: /\.js$/,
loader: 'source-map-loader',
exclude: [ nodeModules ]
},
// in main, load css as raw text
       {
exclude: styles,
test: /\.css$/,
loaders: ['raw-loader', 'postcss-loader']
}, {
exclude: styles,
test: /\.styl$/,
loaders: ['raw-loader', 'postcss-loader', 'stylus-loader'] },
       {
exclude: styles,
test: /\.less$/,
loaders: ['raw-loader', 'postcss-loader', 'less-loader']
}, {
exclude: styles,
test: /\.scss$|\.sass$/,
loaders: ['raw-loader', 'postcss-loader', 'sass-loader']
},


// load global scripts using script-loader
{ include: scripts, test: /\.js$/, loader: 'script-loader' },
{ enforce: 'pre', test: /\.js$/, loader: 'source-map-loader', exclude: [nodeModules] },

       { test: /\.json$/, loader: 'json-loader' },
       { test: /\.(jpg|png|gif)$/, loader: 'url-loader?limit=10000' },
       { test: /\.html$/, loader: 'raw-loader' },
{ test: /\.json$/, loader: 'json-loader' },
{ test: /\.(jpg|png|gif)$/, loader: 'url-loader?limit=10000' },
{ test: /\.html$/, loader: 'raw-loader' },

{ test: /\.(otf|ttf|woff|woff2)$/, loader: 'url-loader?limit=10000' },
{ test: /\.(eot|svg)$/, loader: 'file-loader' }
]
].concat(extraRules)
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(appRoot, appConfig.index),
filename: path.resolve(appConfig.outDir, appConfig.index),
chunksSortMode: packageChunkSort(['inline', 'styles', 'scripts', 'vendor', 'main'])
chunksSortMode: packageChunkSort(['inline', 'styles', 'scripts', 'vendor', 'main']),
excludeChunks: lazyChunks
}),
new BaseHrefWebpackPlugin({
baseHref: baseHref
Expand All @@ -157,14 +159,18 @@ export function getWebpackCommonConfig(
}),
new GlobCopyWebpackPlugin({
patterns: appConfig.assets,
globOptions: {cwd: appRoot, dot: true, ignore: '**/.gitkeep'}
globOptions: { cwd: appRoot, dot: true, ignore: '**/.gitkeep' }
}),
new webpack.LoaderOptionsPlugin({
test: /\.(css|scss|sass|less|styl)$/,
options: {
postcss: [
require('autoprefixer')
]
postcss: [autoprefixer()],
cssLoader: { sourceMap: sourcemap },
sassLoader: { sourceMap: sourcemap },
lessLoader: { sourceMap: sourcemap },
stylusLoader: { sourceMap: sourcemap },
// context needed as a workaround https://github.com/jtangelder/sass-loader/issues/285
context: projectRoot,
},
})
].concat(extraPlugins),
Expand Down
42 changes: 5 additions & 37 deletions packages/angular-cli/models/webpack-build-development.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,18 @@
const path = require('path');

/**
* Enumerate loaders and their dependencies from this file to let the dependency validator
* know they are used.
*
* require('style-loader')
* require('css-loader')
* require('stylus-loader')
* require('less-loader')
* require('sass-loader')
*/
const ExtractTextPlugin = require('extract-text-webpack-plugin');

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 {
output: {
path: path.resolve(projectRoot, appConfig.outDir),
filename: '[name].bundle.js',
sourceMapFilename: '[name].bundle.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']
},
]
}
plugins: [
new ExtractTextPlugin({filename: '[name].bundle.css'})
]
};
};
Loading

0 comments on commit e04a620

Please sign in to comment.