From d494f64e3d2257795c1f85c03a5076ec341cb2ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rokas=20Brazd=C5=BEionis?= Date: Thu, 28 Jul 2016 16:55:44 +0300 Subject: [PATCH] feat: implement --base-href argument Implement --base-href argument for build and github-pages:deploy commands Closes #1064 --- README.md | 11 ++++ addon/ng2/commands/build.ts | 4 +- addon/ng2/commands/github-pages-deploy.ts | 21 +++++++- addon/ng2/models/webpack-build-common.ts | 6 ++- addon/ng2/models/webpack-config.ts | 4 +- addon/ng2/tasks/build-webpack-watch.ts | 2 +- addon/ng2/tasks/build-webpack.ts | 4 +- .../ng2/utilities/base-href-webpack-plugin.ts | 32 ++++++++++++ .../base-href-webpack-plugin.spec.js | 50 +++++++++++++++++++ tests/e2e/e2e_workflow.spec.js | 10 ++++ 10 files changed, 135 insertions(+), 9 deletions(-) create mode 100644 addon/ng2/utilities/base-href-webpack-plugin.ts create mode 100644 tests/acceptance/base-href-webpack-plugin.spec.js diff --git a/README.md b/README.md index 1acd5f83412c..ce0178c2d371 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ The generated project has dependencies that require **Node 4.x.x and NPM 3.x.x** * [Generating a Route](#generating-a-route) * [Creating a Build](#creating-a-build) * [Build Targets and Environment Files](#build-targets-and-environment-files) +* [Base tag handling in index.html](#base-tag-handling-in-indexhtml) * [Adding extra files to the build](#adding-extra-files-to-the-build) * [Running Unit Tests](#running-unit-tests) * [Running End-to-End Tests](#running-end-to-end-tests) @@ -152,6 +153,16 @@ You can also add your own env files other than `dev` and `prod` by doing the fol - add `{ NAME: 'src/environments/environment.NAME.ts' }` to the the `apps[0].environments` object in `angular-cli.json` - use them by using the `--env=NAME` flag on the build/serve commands. +### Base tag handling in index.html + +When building you can modify base tag (``) in your index.html with `--base-href your-url` option. + +```bash +# Sets base tag href to /myUrl/ in your index.html +ng build --base-href /myUrl/ +ng build --bh /myUrl/ +``` + ### Bundling All builds make use of bundling, and using the `--prod` flag in `ng build --prod` diff --git a/addon/ng2/commands/build.ts b/addon/ng2/commands/build.ts index 935102fa8484..4d1eea88ff6f 100644 --- a/addon/ng2/commands/build.ts +++ b/addon/ng2/commands/build.ts @@ -9,6 +9,7 @@ interface BuildOptions { watch?: boolean; watcher?: string; supressSizes: boolean; + baseHref?: string; } module.exports = Command.extend({ @@ -22,7 +23,8 @@ module.exports = Command.extend({ { name: 'output-path', type: 'Path', default: 'dist/', aliases: ['o'] }, { name: 'watch', type: Boolean, default: false, aliases: ['w'] }, { name: 'watcher', type: String }, - { name: 'suppress-sizes', type: Boolean, default: false } + { name: 'suppress-sizes', type: Boolean, default: false }, + { name: 'base-href', type: String, default: null, aliases: ['bh'] }, ], run: function (commandOptions: BuildOptions) { diff --git a/addon/ng2/commands/github-pages-deploy.ts b/addon/ng2/commands/github-pages-deploy.ts index 8a967bff91cf..b96c1e4c9960 100644 --- a/addon/ng2/commands/github-pages-deploy.ts +++ b/addon/ng2/commands/github-pages-deploy.ts @@ -15,6 +15,17 @@ const fsWriteFile = Promise.denodeify(fs.writeFile); const fsReadDir = Promise.denodeify(fs.readdir); const fsCopy = Promise.denodeify(fse.copy); +interface GithubPagesDeployOptions { + message?: string; + target?: string; + environment?: string; + userPage?: boolean; + skipBuild?: boolean; + ghToken?: string; + ghUsername?: string; + baseHref?: string; +} + module.exports = Command.extend({ name: 'github-pages:deploy', aliases: ['gh-pages:deploy'], @@ -57,9 +68,14 @@ module.exports = Command.extend({ type: String, default: '', description: 'Github username' + }, { + name: 'base-href', + type: String, + default: null, + aliases: ['bh'] }], - run: function(options, rawArgs) { + run: function(options: GithubPagesDeployOptions, rawArgs) { var ui = this.ui; var root = this.project.root; var execOptions = { @@ -98,7 +114,8 @@ module.exports = Command.extend({ var buildOptions = { target: options.target, environment: options.environment, - outputPath: outDir + outputPath: outDir, + baseHref: options.baseHref, }; var createGithubRepoTask = new CreateGithubRepo({ diff --git a/addon/ng2/models/webpack-build-common.ts b/addon/ng2/models/webpack-build-common.ts index baeeb5359c27..535470b46b90 100644 --- a/addon/ng2/models/webpack-build-common.ts +++ b/addon/ng2/models/webpack-build-common.ts @@ -5,8 +5,9 @@ import * as webpack from 'webpack'; import * as atl from 'awesome-typescript-loader'; import {findLazyModules} from './find-lazy-modules'; +import { BaseHrefWebpackPlugin } from '../utilities/base-href-webpack-plugin'; -export function getWebpackCommonConfig(projectRoot: string, environment: string, appConfig: any) { +export function getWebpackCommonConfig(projectRoot: string, environment: string, appConfig: any, baseHref: string) { const appRoot = path.resolve(projectRoot, appConfig.root); const appMain = path.resolve(appRoot, appConfig.main); @@ -92,6 +93,9 @@ export function getWebpackCommonConfig(projectRoot: string, environment: string, template: path.resolve(appRoot, appConfig.index), chunksSortMode: 'dependency' }), + new BaseHrefWebpackPlugin({ + baseHref: baseHref + }), new webpack.NormalModuleReplacementPlugin( // This plugin is responsible for swapping the environment files. // Since it takes a RegExp as first parameter, we need to escape the path. diff --git a/addon/ng2/models/webpack-config.ts b/addon/ng2/models/webpack-config.ts index c0768d6f4d00..807d98283333 100644 --- a/addon/ng2/models/webpack-config.ts +++ b/addon/ng2/models/webpack-config.ts @@ -18,13 +18,13 @@ export class NgCliWebpackConfig { private webpackMobileConfigPartial: any; private webpackMobileProdConfigPartial: any; - constructor(public ngCliProject: any, public target: string, public environment: string, outputDir?: string) { + constructor(public ngCliProject: any, public target: string, public environment: string, outputDir?: string, public baseHref?: string) { const config: CliConfig = CliConfig.fromProject(); const appConfig = config.config.apps[0]; appConfig.outDir = outputDir || appConfig.outDir; - this.webpackBaseConfig = getWebpackCommonConfig(this.ngCliProject.root, environment, appConfig); + this.webpackBaseConfig = getWebpackCommonConfig(this.ngCliProject.root, environment, appConfig, baseHref); this.webpackDevConfigPartial = getWebpackDevConfigPartial(this.ngCliProject.root, appConfig); this.webpackProdConfigPartial = getWebpackProdConfigPartial(this.ngCliProject.root, appConfig); diff --git a/addon/ng2/tasks/build-webpack-watch.ts b/addon/ng2/tasks/build-webpack-watch.ts index 911b6f3b0936..958ba65cc52b 100644 --- a/addon/ng2/tasks/build-webpack-watch.ts +++ b/addon/ng2/tasks/build-webpack-watch.ts @@ -16,7 +16,7 @@ module.exports = Task.extend({ rimraf.sync(path.resolve(project.root, runTaskOptions.outputPath)); - const config = new NgCliWebpackConfig(project, runTaskOptions.target, runTaskOptions.environment, runTaskOptions.outputPath).config; + const config = new NgCliWebpackConfig(project, runTaskOptions.target, runTaskOptions.environment, runTaskOptions.outputPath, runTaskOptions.baseHref).config; const webpackCompiler = webpack(config); webpackCompiler.apply(new ProgressPlugin({ diff --git a/addon/ng2/tasks/build-webpack.ts b/addon/ng2/tasks/build-webpack.ts index fe0ec60fe59f..04614f6a14bd 100644 --- a/addon/ng2/tasks/build-webpack.ts +++ b/addon/ng2/tasks/build-webpack.ts @@ -11,12 +11,12 @@ let lastHash: any = null; module.exports = Task.extend({ // Options: String outputPath - run: function(runTaskOptions: ServeTaskOptions) { + run: function (runTaskOptions: ServeTaskOptions) { var project = this.cliProject; rimraf.sync(path.resolve(project.root, runTaskOptions.outputPath)); - var config = new NgCliWebpackConfig(project, runTaskOptions.target, runTaskOptions.environment, runTaskOptions.outputPath).config; + var config = new NgCliWebpackConfig(project, runTaskOptions.target, runTaskOptions.environment, runTaskOptions.outputPath, runTaskOptions.baseHref).config; const webpackCompiler = webpack(config); diff --git a/addon/ng2/utilities/base-href-webpack-plugin.ts b/addon/ng2/utilities/base-href-webpack-plugin.ts new file mode 100644 index 000000000000..a5f5ae1556cb --- /dev/null +++ b/addon/ng2/utilities/base-href-webpack-plugin.ts @@ -0,0 +1,32 @@ +interface BaseHrefWebpackPluginOptions { + baseHref: string; +} + +export class BaseHrefWebpackPlugin { + constructor(private options: BaseHrefWebpackPluginOptions) { } + + apply(compiler): void { + // Ignore if baseHref is not passed + if (!this.options.baseHref) { + return; + } + + compiler.plugin('compilation', (compilation) => { + compilation.plugin('html-webpack-plugin-before-html-processing', (htmlPluginData, callback) => { + // Check if base tag already exists + const baseTagRegex = //i; + const baseTagMatches = htmlPluginData.html.match(baseTagRegex); + if (!baseTagMatches) { + // Insert it in top of the head if not exist + htmlPluginData.html = htmlPluginData.html.replace(//i, '$&' + ``); + } else { + // Replace only href attribute if exists + const modifiedBaseTag = baseTagMatches[0].replace(/href="\S+"/i, `href="${this.options.baseHref}"`); + htmlPluginData.html = htmlPluginData.html.replace(baseTagRegex, modifiedBaseTag); + } + + callback(null, htmlPluginData); + }); + }); + } +} \ No newline at end of file diff --git a/tests/acceptance/base-href-webpack-plugin.spec.js b/tests/acceptance/base-href-webpack-plugin.spec.js new file mode 100644 index 000000000000..58bf73442e25 --- /dev/null +++ b/tests/acceptance/base-href-webpack-plugin.spec.js @@ -0,0 +1,50 @@ +/*eslint-disable no-console */ +'use strict'; + +var expect = require('chai').expect; +var BaseHrefWebpackPlugin = require('../../addon/ng2/utilities/base-href-webpack-plugin').BaseHrefWebpackPlugin; + +function mockCompiler(indexHtml, callback) { + return { + plugin: function (event, compilerCallback) { + var compilation = { + plugin: function (hook, compilationCallback) { + var htmlPluginData = { + html: indexHtml + }; + compilationCallback(htmlPluginData, callback); + } + }; + compilerCallback(compilation); + } + }; +} + +describe('base href webpack plugin', function () { + it('should do nothing when baseHref is null', function () { + var plugin = new BaseHrefWebpackPlugin({ baseHref: null }); + + var compiler = mockCompiler('', function (x, htmlPluginData) { + expect(htmlPluginData.html).to.equal(''); + }); + plugin.apply(compiler); + }); + + it('should insert base tag when not exist', function () { + var plugin = new BaseHrefWebpackPlugin({ baseHref: '/' }); + + var compiler = mockCompiler('', function (x, htmlPluginData) { + expect(htmlPluginData.html).to.equal(''); + }); + plugin.apply(compiler); + }); + + it('should replace href attribute when base tag already exists', function () { + var plugin = new BaseHrefWebpackPlugin({ baseHref: '/myUrl/' }); + + var compiler = mockCompiler('', function (x, htmlPluginData) { + expect(htmlPluginData.html).to.equal(''); + }); + plugin.apply(compiler); + }); +}); diff --git a/tests/e2e/e2e_workflow.spec.js b/tests/e2e/e2e_workflow.spec.js index a92ec8cc6501..814c39f9428f 100644 --- a/tests/e2e/e2e_workflow.spec.js +++ b/tests/e2e/e2e_workflow.spec.js @@ -128,6 +128,16 @@ describe('Basic end-to-end Workflow', function () { }); }); + it('Supports base tag modifications via `ng build --base-href`', function() { + this.timeout(420000); + + sh.exec(`${ngBin} build --base-href /myUrl/`); + const indexHtmlPath = path.join(process.cwd(), 'dist/index.html'); + const indexHtml = fs.readFileSync(indexHtmlPath, { encoding: 'utf8' }); + + expect(indexHtml).to.match(/