Skip to content

Commit

Permalink
feat(build): add style paths
Browse files Browse the repository at this point in the history
Add `paths/includePaths` functionality for `sass` and `stylus`.

Similar functionality for less is blocked by
webpack-contrib/less-loader#75.

To add paths, use the new entry in `angular-cli.json` app object:
```
"stylePreprocessorOptions": {
  "includePaths": [
    "style-paths"
  ]
},
```

Fix angular#1791
  • Loading branch information
filipesilva committed Jan 19, 2017
1 parent 48d1e44 commit c181c0c
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 146 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
"walk-sync": "^0.2.6",
"webpack": "2.2.0",
"webpack-dev-server": "2.2.0-rc.0",
"webpack-merge": "^0.14.0",
"webpack-merge": "^2.4.0",
"webpack-sources": "^0.1.3",
"zone.js": "^0.7.2"
},
Expand Down
15 changes: 15 additions & 0 deletions packages/angular-cli/lib/config/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,21 @@
},
"additionalProperties": false
},
"stylePreprocessorOptions": {
"description": "Options to pass to style preprocessors",
"type": "object",
"properties": {
"includePaths": {
"description": "Paths to include. Paths will be resolved to project root.",
"type": "array",
"items": {
"type": "string"
},
"default": []
}
},
"additionalProperties": false
},
"scripts": {
"description": "Global scripts to be included in the build.",
"type": "array",
Expand Down
85 changes: 18 additions & 67 deletions packages/angular-cli/models/webpack-build-common.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import * as webpack from 'webpack';
import * as path from 'path';
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, getOutputHashFormat } from './webpack-build-utils';
import { extraEntryParser, lazyChunksFilter, getOutputHashFormat } from './webpack-build-utils';

const autoprefixer = require('autoprefixer');
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
Expand Down Expand Up @@ -33,19 +32,22 @@ export function getWebpackCommonConfig(
vendorChunk: boolean,
verbose: boolean,
progress: boolean,
outputHashing: string,
extractCss: boolean,
outputHashing: string
) {

const appRoot = path.resolve(projectRoot, appConfig.root);
const nodeModules = path.resolve(projectRoot, 'node_modules');

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

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

// figure out which are the lazy loaded entry points
const lazyChunks = lazyChunksFilter([
...extraEntryParser(appConfig.scripts, appRoot, 'scripts'),
...extraEntryParser(appConfig.styles, appRoot, 'styles')
]);

if (appConfig.main) {
entryPoints['main'] = [path.resolve(appRoot, appConfig.main)];
}
Expand All @@ -54,51 +56,22 @@ export function getWebpackCommonConfig(
const hashFormat = getOutputHashFormat(outputHashing);

// process global scripts
if (appConfig.scripts && appConfig.scripts.length > 0) {
if (appConfig.scripts.length > 0) {
const globalScripts = extraEntryParser(appConfig.scripts, appRoot, 'scripts');

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

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

// process global styles
if (!appConfig.styles || 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 (extractCss && 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({
name: 'vendor',
Expand Down Expand Up @@ -157,26 +130,16 @@ export function getWebpackCommonConfig(
module: {
rules: [
{ enforce: 'pre', test: /\.js$/, loader: 'source-map-loader', exclude: [nodeModules] },

{ test: /\.json$/, loader: 'json-loader' },
{
test: /\.(jpg|png|gif)$/,
loader: `url-loader?name=[name]${hashFormat.file}.[ext]&limit=10000`
},
{ test: /\.html$/, loader: 'raw-loader' },

{ test: /\.(eot|svg)$/, loader: `file-loader?name=[name]${hashFormat.file}.[ext]` },
{
test: /\.(otf|ttf|woff|woff2)$/,
test: /\.(jpg|png|gif|otf|ttf|woff|woff2)$/,
loader: `url-loader?name=[name]${hashFormat.file}.[ext]&limit=10000`
},
{ test: /\.(eot|svg)$/, loader: `file-loader?name=[name]${hashFormat.file}.[ext]` }
}
].concat(extraRules)
},
plugins: [
new ExtractTextPlugin({
filename: `[name]${hashFormat.extract}.bundle.css`,
disable: !extractCss
}),
new HtmlWebpackPlugin({
template: path.resolve(appRoot, appConfig.index),
filename: path.resolve(appConfig.outDir, appConfig.index),
Expand All @@ -190,18 +153,6 @@ export function getWebpackCommonConfig(
new webpack.optimize.CommonsChunkPlugin({
minChunks: Infinity,
name: 'inline'
}),
new webpack.LoaderOptionsPlugin({
test: /\.(css|scss|sass|less|styl)$/,
options: {
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),
node: {
Expand Down
19 changes: 1 addition & 18 deletions packages/angular-cli/models/webpack-build-production.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import * as path from 'path';
import * as webpack from 'webpack';
import {CompressionPlugin} from '../lib/webpack/compression-plugin';
const autoprefixer = require('autoprefixer');
const postcssDiscardComments = require('postcss-discard-comments');


export const getWebpackProdConfigPartial = function(projectRoot: string,
appConfig: any,
Expand All @@ -26,22 +25,6 @@ export const getWebpackProdConfigPartial = function(projectRoot: string,
algorithm: 'gzip',
test: /\.js$|\.html$|\.css$/,
threshold: 10240
}),
// LoaderOptionsPlugin needs to be fully duplicated because webpackMerge will replace it.
new webpack.LoaderOptionsPlugin({
test: /\.(css|scss|sass|less|styl)$/,
options: {
postcss: [
autoprefixer(),
postcssDiscardComments
],
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,
}
})
]
};
Expand Down
133 changes: 133 additions & 0 deletions packages/angular-cli/models/webpack-build-styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import * as webpack from 'webpack';
import * as path from 'path';
import {
SuppressExtractedTextChunksWebpackPlugin
} from '../plugins/suppress-entry-chunks-webpack-plugin';
import { extraEntryParser, getOutputHashFormat } from './webpack-build-utils';

const postcssDiscardComments = require('postcss-discard-comments');
const autoprefixer = require('autoprefixer');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

/**
* Enumerate loaders and their dependencies from this file to let the dependency validator
* know they are used.
*
* require('raw-loader')
* require('style-loader')
* require('postcss-loader')
* require('css-loader')
* require('stylus')
* require('stylus-loader')
* require('less')
* require('less-loader')
* require('node-sass')
* require('sass-loader')
*/

export function getWebpackStylesConfig(
projectRoot: string,
appConfig: any,
target: string,
sourcemap: boolean,
outputHashing: string,
extractCss: boolean,
) {

const appRoot = path.resolve(projectRoot, appConfig.root);
const entryPoints: { [key: string]: string[] } = {};
const globalStylePaths: string[] = [];
const extraPlugins: any[] = [];

// discard comments in production
const extraPostCssPlugins = target === 'production' ? [postcssDiscardComments] : [];

// determine hashing format
const hashFormat = getOutputHashFormat(outputHashing);

// use includePaths from appConfig
const includePaths: string [] = [];

if (appConfig.stylePreprocessorOptions
&& appConfig.stylePreprocessorOptions.includePaths
&& appConfig.stylePreprocessorOptions.includePaths.length > 0
) {
appConfig.stylePreprocessorOptions.includePaths.forEach((includePath: string) =>
includePaths.push(path.resolve(appRoot, includePath)));
}

// process global styles
if (appConfig.styles.length > 0) {
const globalStyles = extraEntryParser(appConfig.styles, appRoot, 'styles');
// add style entry points
globalStyles.forEach(style =>
entryPoints[style.entry]
? entryPoints[style.entry].push(style.path)
: entryPoints[style.entry] = [style.path]
);
// add global css paths
globalStylePaths.push(...globalStyles.map((style) => style.path));
}

// set base rules to derive final rules from
const baseRules = [
{ test: /\.css$/, loaders: [] },
{ test: /\.scss$|\.sass$/, loaders: ['sass-loader'] },
{ test: /\.less$/, loaders: ['less-loader'] },
// stylus-loader doesn't support webpack.LoaderOptionsPlugin properly,
// so we need to add options in it's query
{ test: /\.styl$/, loaders: [`stylus-loader?${JSON.stringify({
sourceMap: sourcemap,
paths: includePaths
})}`] }
];

const commonLoaders = ['postcss-loader'];

// load component css as raw strings
let rules: any = baseRules.map(({test, loaders}) => ({
exclude: globalStylePaths, test, loaders: ['raw-loader', ...commonLoaders, ...loaders]
}));

// load global css as css files
if (globalStylePaths.length > 0) {
rules.push(...baseRules.map(({test, loaders}) => ({
include: globalStylePaths, test, loaders: ExtractTextPlugin.extract({
remove: false,
loader: ['css-loader', ...commonLoaders, ...loaders],
fallbackLoader: 'style-loader',
// publicPath needed as a workaround https://github.com/angular/angular-cli/issues/4035
publicPath: ''
})
})));
}

// supress empty .js files in css only entry points
if (extractCss) {
extraPlugins.push(new SuppressExtractedTextChunksWebpackPlugin());
}

return {
entry: entryPoints,
module: { rules },
plugins: [
// extract global css from js files into own css file
new ExtractTextPlugin({
filename: `[name]${hashFormat.extract}.bundle.css`,
disable: !extractCss
}),
new webpack.LoaderOptionsPlugin({
options: {
postcss: [autoprefixer()].concat(extraPostCssPlugins),
cssLoader: { sourceMap: sourcemap },
sassLoader: { sourceMap: sourcemap, includePaths },
// less-loader doesn't support paths
lessLoader: { sourceMap: sourcemap },
// stylus-loader doesn't support LoaderOptionsPlugin properly, options in query instead
// context needed as a workaround https://github.com/jtangelder/sass-loader/issues/285
context: projectRoot,
},
})
].concat(extraPlugins)
};
}
Loading

0 comments on commit c181c0c

Please sign in to comment.