diff --git a/addon/ng2/blueprints/ng2/files/__path__/index.html b/addon/ng2/blueprints/ng2/files/__path__/index.html index 250c1d86e223..3fb880681207 100644 --- a/addon/ng2/blueprints/ng2/files/__path__/index.html +++ b/addon/ng2/blueprints/ng2/files/__path__/index.html @@ -4,7 +4,6 @@ <%= jsComponentName %> - <% if (isMobile) { %> @@ -22,9 +21,15 @@ <% } %> - + <% if (!arguments[0].files) { + print (`<## _.forEach(files.css, css => { ##><## }) ##>`); + } %> <<%= prefix %>-root>Loading...-root> + + <% if (!arguments[0].files) { + print (`<## _.forEach(files.js, js => { ##><## }) ##>`); + } %> diff --git a/addon/ng2/blueprints/ng2/files/__path__/style.__styleext__ b/addon/ng2/blueprints/ng2/files/__path__/style.__styleext__ new file mode 100644 index 000000000000..4aba530ad073 --- /dev/null +++ b/addon/ng2/blueprints/ng2/files/__path__/style.__styleext__ @@ -0,0 +1,3 @@ +body <% if (styleExt !== 'sass') { %>{<% } %> + background: #F9F9F9; +<% if (styleExt !== 'sass') { %>}<% } %> diff --git a/addon/ng2/blueprints/ng2/files/angular-cli.json b/addon/ng2/blueprints/ng2/files/angular-cli.json index 5b96ba0ba88c..52cc3bcc9e9b 100644 --- a/addon/ng2/blueprints/ng2/files/angular-cli.json +++ b/addon/ng2/blueprints/ng2/files/angular-cli.json @@ -7,7 +7,10 @@ { "main": "<%= sourceDir %>/main.ts", "tsconfig": "<%= sourceDir %>/tsconfig.json", - "mobile": <%= isMobile %> + "mobile": <%= isMobile %>, + "styles": { + "src/style.<%= styleExt %>": { "output": "style.css", "autoImported": true } + } } ], "addons": [], diff --git a/addon/ng2/models/index.ts b/addon/ng2/models/index.ts index f01dfadc6be5..39b5d1f5fce4 100644 --- a/addon/ng2/models/index.ts +++ b/addon/ng2/models/index.ts @@ -4,3 +4,4 @@ export * from './webpack-build-production'; export * from './webpack-build-development'; export * from './webpack-build-mobile'; export * from './webpack-build-utils'; +export * from './webpack-build-css'; diff --git a/addon/ng2/models/webpack-build-common.ts b/addon/ng2/models/webpack-build-common.ts index 4871410f9be7..47735600878a 100644 --- a/addon/ng2/models/webpack-build-common.ts +++ b/addon/ng2/models/webpack-build-common.ts @@ -1,12 +1,12 @@ import * as path from 'path'; import * as CopyWebpackPlugin from 'copy-webpack-plugin'; -import * as HtmlWebpackPlugin from 'html-webpack-plugin'; import * as webpack from 'webpack'; import { ForkCheckerPlugin } from 'awesome-typescript-loader'; import { CliConfig } from './config'; export function getWebpackCommonConfig(projectRoot: string, sourceDir: string) { return { + name: 'main', devtool: 'inline-source-map', resolve: { extensions: ['', '.ts', '.js'], @@ -60,10 +60,6 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string) { }, plugins: [ new ForkCheckerPlugin(), - new HtmlWebpackPlugin({ - template: path.resolve(projectRoot, `./${sourceDir}/index.html`), - chunksSortMode: 'dependency' - }), new webpack.optimize.CommonsChunkPlugin({ name: ['polyfills'] }), @@ -73,7 +69,10 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string) { filename: 'inline.js', sourceMapFilename: 'inline.map' }), - new CopyWebpackPlugin([{from: path.resolve(projectRoot, './public'), to: path.resolve(projectRoot, './dist')}]) + new CopyWebpackPlugin([{ + from: path.resolve(projectRoot, './public'), + to: path.resolve(projectRoot, './dist') + }]) ], node: { global: 'window', diff --git a/addon/ng2/models/webpack-build-css.ts b/addon/ng2/models/webpack-build-css.ts new file mode 100644 index 000000000000..d305c1f178a2 --- /dev/null +++ b/addon/ng2/models/webpack-build-css.ts @@ -0,0 +1,41 @@ +import * as path from 'path'; +import * as ExtractTextPlugin from 'extract-text-webpack-plugin'; +import * as existsSync from 'exists-sync'; +import { CliConfig } from './config'; + +export function getWebpackCSSConfig(projectRoot: string, sourceDir: string) { + const styles = CliConfig.fromProject().apps.map(app => app.styles); + let entries = {}; + + styles.forEach(style => { + for (let src in style) { + if (existsSync(path.resolve(projectRoot, src))) { + entries[style[src].output] = path.resolve(projectRoot, `./${src}`); + } + } + }); + + return { + name: 'styles', + resolve: { + root: path.resolve(projectRoot) + }, + context: path.resolve(__dirname), + entry: entries, + output: { + path: path.resolve(projectRoot, './dist'), + filename: '[name]' + }, + module: { + loaders: [ + { test: /\.css$/i, loader: ExtractTextPlugin.extract(['css-loader']) }, + { test: /\.sass$|\.scss$/i, loader: ExtractTextPlugin.extract(['css-loader', 'sass-loader']) }, + { test: /\.less$/i, loader: ExtractTextPlugin.extract(['css-loader', 'less-loader']) }, + { test: /\.styl$/i, loader: ExtractTextPlugin.extract(['css-loader', 'stylus-loader']) } + ] + }, + plugins: [ + new ExtractTextPlugin('[name]') + ] + } +}; diff --git a/addon/ng2/models/webpack-build-mobile.ts b/addon/ng2/models/webpack-build-mobile.ts index 0eb18933ddcb..3eab1710e282 100644 --- a/addon/ng2/models/webpack-build-mobile.ts +++ b/addon/ng2/models/webpack-build-mobile.ts @@ -2,7 +2,6 @@ import * as webpack from 'webpack'; import * as path from 'path'; import * as OfflinePlugin from 'offline-plugin'; import * as CopyWebpackPlugin from 'copy-webpack-plugin'; -import { PrerenderWebpackPlugin } from '../utilities/prerender-webpack-plugin.ts'; import { CliConfig } from './config'; export const getWebpackMobileConfigPartial = function (projectRoot: string, sourceDir: string) { @@ -11,12 +10,7 @@ export const getWebpackMobileConfigPartial = function (projectRoot: string, sour new CopyWebpackPlugin([ {from: path.resolve(projectRoot, `./${sourceDir}/icons`), to: path.resolve(projectRoot, './dist/icons')}, {from: path.resolve(projectRoot, `./${sourceDir}/manifest.webapp`), to: path.resolve(projectRoot, './dist')} - ]), - new PrerenderWebpackPlugin({ - templatePath: 'index.html', - configPath: path.resolve(projectRoot, `./${sourceDir}/main-app-shell.ts`), - appPath: path.resolve(projectRoot, `./${sourceDir}`) - }) + ]) ] } }; diff --git a/addon/ng2/models/webpack-config.ts b/addon/ng2/models/webpack-config.ts index a215217c0afa..639919459397 100644 --- a/addon/ng2/models/webpack-config.ts +++ b/addon/ng2/models/webpack-config.ts @@ -1,6 +1,8 @@ import * as path from 'path'; import * as fs from 'fs'; import * as webpackMerge from 'webpack-merge'; +import * as AssetsPlugin from 'assets-webpack-plugin'; +import { HtmlCliPlugin } from '../utilities/html-cli-plugin'; import { CliConfig } from './config'; import { NgCliEnvironmentPlugin } from '../utilities/environment-plugin'; import { @@ -9,6 +11,7 @@ import { getWebpackProdConfigPartial, getWebpackMobileConfigPartial, getWebpackMobileProdConfigPartial + getWebpackCSSConfig } from './'; export class NgCliWebpackConfig { @@ -22,6 +25,7 @@ export class NgCliWebpackConfig { private webpackMaterialE2EConfig: any; private webpackMobileConfigPartial: any; private webpackMobileProdConfigPartial: any; + private webpackCSSConfig: any; constructor(public ngCliProject: any, public target: string, public environment: string) { const sourceDir = CliConfig.fromProject().defaults.sourceDir; @@ -31,6 +35,7 @@ export class NgCliWebpackConfig { this.webpackBaseConfig = getWebpackCommonConfig(this.ngCliProject.root, sourceDir); this.webpackDevConfigPartial = getWebpackDevConfigPartial(this.ngCliProject.root, sourceDir); this.webpackProdConfigPartial = getWebpackProdConfigPartial(this.ngCliProject.root, sourceDir); + this.webpackCSSConfig = getWebpackCSSConfig(this.ngCliProject.root, sourceDir); if (CliConfig.fromProject().apps[0].mobile){ this.webpackMobileConfigPartial = getWebpackMobileConfigPartial(this.ngCliProject.root, sourceDir); @@ -40,20 +45,34 @@ export class NgCliWebpackConfig { } this.generateConfig(); - this.config.plugins.unshift(new NgCliEnvironmentPlugin({ + this.config[0].plugins.unshift(new NgCliEnvironmentPlugin({ path: path.resolve(this.ngCliProject.root, `./${sourceDir}/app/environments/`), src: 'environment.ts', dest: `environment.${this.environment}.ts` })); + + const assetsPluginInstance = new AssetsPlugin({ filename: 'cli.assets.json' }); + const htmlCliPlugin = new HtmlCliPlugin(); + + this.config.forEach(conf => { + conf.plugins.unshift(assetsPluginInstance); + conf.plugins.unshift(htmlCliPlugin); + }); } generateConfig(): void { switch (this.target) { case "development": - this.config = webpackMerge(this.webpackBaseConfig, this.webpackDevConfigPartial); + this.config = [ + webpackMerge(this.webpackBaseConfig, this.webpackDevConfigPartial), + this.webpackCSSConfig + ]; break; case "production": - this.config = webpackMerge(this.webpackBaseConfig, this.webpackProdConfigPartial); + this.config = [ + webpackMerge(this.webpackBaseConfig, this.webpackProdConfigPartial), + this.webpackCSSConfig + ]; break; default: throw new Error("Invalid build target. Only 'development' and 'production' are available."); diff --git a/addon/ng2/tasks/serve-webpack.ts b/addon/ng2/tasks/serve-webpack.ts index 60589e48c011..5b4ee9344d45 100644 --- a/addon/ng2/tasks/serve-webpack.ts +++ b/addon/ng2/tasks/serve-webpack.ts @@ -18,7 +18,7 @@ module.exports = Task.extend({ var config: NgCliWebpackConfig = new NgCliWebpackConfig(this.project, commandOptions.target, commandOptions.environment).config; // This allows for live reload of page when changes are made to repo. // https://webpack.github.io/docs/webpack-dev-server.html#inline-mode - config.entry.main.unshift(`webpack-dev-server/client?http://${commandOptions.host}:${commandOptions.port}/`); + config[0].entry.main.unshift(`webpack-dev-server/client?http://${commandOptions.host}:${commandOptions.port}/`); webpackCompiler = webpack(config); webpackCompiler.apply(new ProgressPlugin({ diff --git a/addon/ng2/utilities/html-cli-plugin.ts b/addon/ng2/utilities/html-cli-plugin.ts new file mode 100644 index 000000000000..e165abfe5482 --- /dev/null +++ b/addon/ng2/utilities/html-cli-plugin.ts @@ -0,0 +1,70 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as _ from 'lodash'; +import * as MemoryFS from 'memory-fs'; +import { CliConfig } from '../models/config'; + +export class HtmlCliPlugin { + private cachedTemplate: string; + + apply(compiler) { + if (compiler.options.name === 'main' && CliConfig.fromProject().apps[0].mobile) { + compiler.plugin('emit', this.cacheTemplate); + } + compiler.plugin('done', this.generateIndexHtml); + } + + cacheTemplate(c, callback) { + const sourceDir = CliConfig.fromProject().defaults.sourceDir; + const projectRoot = path.resolve(sourceDir, '..'); + const appShellPath = path.resolve(projectRoot, `./${sourceDir}/main-app-shell.ts`); + const appShell = require(appShellPath); + const bootloader = appShell.getBootloader(); + const indexHtml = path.resolve(sourceDir, 'index.html'); + const assetsPath = path.resolve(projectRoot, 'cli.assets.json'); + + appShell.serialize(bootloader, fs.readFileSync(indexHtml, 'utf8')).then(html => { + console.log(html) + this.cachedTemplate = html; + callback(); + }); + } + + generateIndexHtml(stats) { + const isMobile = CliConfig.fromProject().apps[0].mobile; + const sourceDir = CliConfig.fromProject().defaults.sourceDir; + const projectRoot = path.resolve(sourceDir, '..'); + const assetsPath = path.resolve(projectRoot, 'cli.assets.json'); + const contents = JSON.parse(fs.readFileSync(assetsPath, 'utf8')); + const indexHtml = path.resolve(sourceDir, 'index.html'); + let tpl; + let mfs; + let files = { js: [], css: [] }; + + Object.keys(contents).forEach(key => { + let type = Object.keys(contents[key])[0]; + files[type].unshift(contents[key][type]); + }); + + if (this.cachedTemplate) { + tpl = this.cachedTemplate; + tpl = _.template(tpl.replace(/</g, '<').replace(/>/g, '>').replace(/##/g, '%')); + writeTemplate(stats, tpl, files); + } else { + tpl = _.template(fs.readFileSync(indexHtml, 'utf8').replace(/##/g, '%')); + writeTemplate(stats, tpl, files); + } + + function writeTemplate(stats, tpl, files) { + const destHtml = path.resolve(stats.compilation.compiler.outputPath, 'index.html'); + + if (stats.compilation.compiler.outputFileSystem instanceof MemoryFS) { + if (!mfs) { mfs = stats.compilation.compiler.outputFileSystem; } + mfs.writeFileSync(destHtml, tpl({ files: files }), 'utf8'); + } else { + fs.writeFileSync(destHtml, tpl({ files: files }), 'utf8'); + } + } + } + +} diff --git a/package.json b/package.json index d6c4d54b3990..ec422a234ff0 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@types/rimraf": "0.0.25-alpha", "@types/webpack": "^1.12.22-alpha", "angular2-template-loader": "^0.4.0", + "assets-webpack-plugin": "^3.4.0", "awesome-typescript-loader": "^2.1.1", "chalk": "^1.1.3", "compression-webpack-plugin": "^0.3.1", @@ -48,11 +49,11 @@ "exit": "^0.1.2", "exports-loader": "^0.6.3", "expose-loader": "^0.7.1", + "extract-text-webpack-plugin": "^2.0.0-beta.3", "file-loader": "^0.8.5", "fs-extra": "^0.30.0", "glob": "^7.0.3", "handlebars": "^4.0.5", - "html-webpack-plugin": "^2.19.0", "istanbul-instrumenter-loader": "^0.2.0", "json-loader": "^0.5.4", "karma-sourcemap-loader": "^0.3.7", @@ -74,7 +75,7 @@ "remap-istanbul": "^0.6.4", "resolve": "^1.1.7", "rimraf": "^2.5.3", - "sass-loader": "^3.2.0", + "sass-loader": "^3.2.3", "shelljs": "^0.7.0", "silent-error": "^1.0.0", "source-map-loader": "^0.1.5",