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 423788459318..a976372531ff 100644
--- a/addon/ng2/commands/build.ts
+++ b/addon/ng2/commands/build.ts
@@ -2,13 +2,14 @@ import * as Command from 'ember-cli/lib/models/command';
import * as WebpackBuild from '../tasks/build-webpack';
import * as WebpackBuildWatch from '../tasks/build-webpack-watch';
-interface BuildOptions {
+export interface BuildOptions {
target?: string;
environment?: string;
outputPath?: string;
watch?: boolean;
watcher?: string;
supressSizes: boolean;
+ baseHref?: string;
}
module.exports = Command.extend({
@@ -27,7 +28,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 c0420298f83b..c8c3ec400038 100644
--- a/addon/ng2/commands/github-pages-deploy.ts
+++ b/addon/ng2/commands/github-pages-deploy.ts
@@ -11,11 +11,20 @@ import * as CreateGithubRepo from '../tasks/create-github-repo';
import { CliConfig } from '../models/config';
import { oneLine } from 'common-tags';
-const fsReadFile = Promise.denodeify(fs.readFile);
-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'],
@@ -61,9 +70,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) {
const ui = this.ui;
const root = this.project.root;
const execOptions = {
@@ -99,10 +113,19 @@ module.exports = Command.extend({
outputPath: outDir
});
+ /**
+ * BaseHref tag setting logic:
+ * First, use --base-href flag value if provided.
+ * Else if --user-page is true, then keep baseHref default as declared in index.html.
+ * Otherwise auto-replace with `/${projectName}/`.
+ */
+ const baseHref = options.baseHref || (options.userPage ? null : `/${projectName}/`);
+
const buildOptions = {
target: options.target,
environment: options.environment,
- outputPath: outDir
+ outputPath: outDir,
+ baseHref: baseHref,
};
const createGithubRepoTask = new CreateGithubRepo({
@@ -123,7 +146,7 @@ module.exports = Command.extend({
.then(createGitHubRepoIfNeeded)
.then(checkoutGhPages)
.then(copyFiles)
- .then(updateBaseHref)
+ .then(createNotFoundPage)
.then(addAndCommit)
.then(returnStartingBranch)
.then(pushToGitRepo)
@@ -191,15 +214,10 @@ module.exports = Command.extend({
})));
}
- function updateBaseHref() {
- if (options.userPage) { return Promise.resolve(); }
- let indexHtml = path.join(root, 'index.html');
- return fsReadFile(indexHtml, 'utf8')
- .then((data) => data.replace(//g, ``))
- .then((data) => {
- fsWriteFile(indexHtml, data, 'utf8');
- fsWriteFile(path.join(root, '404.html'), data, 'utf8');
- });
+ function createNotFoundPage() {
+ const indexHtml = path.join(root, 'index.html');
+ const notFoundPage = path.join(root, '404.html');
+ return fsCopy(indexHtml, notFoundPage);
}
function addAndCommit() {
diff --git a/addon/ng2/models/webpack-build-common.ts b/addon/ng2/models/webpack-build-common.ts
index 7bc30b3eef3e..2abd5822e5b1 100644
--- a/addon/ng2/models/webpack-build-common.ts
+++ b/addon/ng2/models/webpack-build-common.ts
@@ -5,8 +5,14 @@ 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);
@@ -118,6 +124,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 2da873f98df5..fd292f9aaeaa 100644
--- a/addon/ng2/models/webpack-config.ts
+++ b/addon/ng2/models/webpack-config.ts
@@ -20,14 +20,20 @@ export class NgCliWebpackConfig {
public ngCliProject: any,
public target: string,
public environment: string,
- outputDir?: string
+ outputDir?: string,
+ baseHref?: string
) {
const config: CliConfig = CliConfig.fromProject();
const appConfig = config.config.apps[0];
appConfig.outDir = outputDir || appConfig.outDir;
- this.baseConfig = getWebpackCommonConfig(this.ngCliProject.root, environment, appConfig);
+ this.baseConfig = getWebpackCommonConfig(
+ this.ngCliProject.root,
+ environment,
+ appConfig,
+ baseHref
+ );
this.devConfigPartial = getWebpackDevConfigPartial(this.ngCliProject.root, appConfig);
this.prodConfigPartial = getWebpackProdConfigPartial(this.ngCliProject.root, appConfig);
diff --git a/addon/ng2/tasks/build-webpack-watch.ts b/addon/ng2/tasks/build-webpack-watch.ts
index af350d17e0ba..c25f8a7d80a5 100644
--- a/addon/ng2/tasks/build-webpack-watch.ts
+++ b/addon/ng2/tasks/build-webpack-watch.ts
@@ -5,12 +5,12 @@ import * as webpack from 'webpack';
import * as ProgressPlugin from 'webpack/lib/ProgressPlugin';
import { NgCliWebpackConfig } from '../models/webpack-config';
import { webpackOutputOptions } from '../models/';
-import { ServeTaskOptions } from '../commands/serve';
+import { BuildOptions } from '../commands/build';
let lastHash: any = null;
module.exports = Task.extend({
- run: function(runTaskOptions: ServeTaskOptions) {
+ run: function(runTaskOptions: BuildOptions) {
const project = this.cliProject;
@@ -20,7 +20,8 @@ module.exports = Task.extend({
project,
runTaskOptions.target,
runTaskOptions.environment,
- runTaskOptions.outputPath
+ runTaskOptions.outputPath,
+ runTaskOptions.baseHref
).config;
const webpackCompiler = webpack(config);
diff --git a/addon/ng2/tasks/build-webpack.ts b/addon/ng2/tasks/build-webpack.ts
index e0a3316a5cd2..b8cf8fa83d7c 100644
--- a/addon/ng2/tasks/build-webpack.ts
+++ b/addon/ng2/tasks/build-webpack.ts
@@ -2,7 +2,7 @@ import * as rimraf from 'rimraf';
import * as path from 'path';
import * as Task from 'ember-cli/lib/models/task';
import * as webpack from 'webpack';
-import { ServeTaskOptions } from '../commands/serve';
+import { BuildOptions } from '../commands/build';
import { NgCliWebpackConfig } from '../models/webpack-config';
import { webpackOutputOptions } from '../models/';
@@ -11,7 +11,7 @@ let lastHash: any = null;
module.exports = Task.extend({
// Options: String outputPath
- run: function(runTaskOptions: ServeTaskOptions) {
+ run: function (runTaskOptions: BuildOptions) {
const project = this.cliProject;
@@ -20,7 +20,8 @@ module.exports = Task.extend({
project,
runTaskOptions.target,
runTaskOptions.environment,
- runTaskOptions.outputPath
+ 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..8466882adc53
--- /dev/null
+++ b/addon/ng2/utilities/base-href-webpack-plugin.ts
@@ -0,0 +1,39 @@
+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);
+ }
+ );
+ });
+ }
+}
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/acceptance/github-pages-deploy.spec.js b/tests/acceptance/github-pages-deploy.spec.js
index 3098a2cf4083..28358cc40a3a 100644
--- a/tests/acceptance/github-pages-deploy.spec.js
+++ b/tests/acceptance/github-pages-deploy.spec.js
@@ -14,7 +14,6 @@ var https = require('https');
var SilentError = require('silent-error');
const expect = chai.expect;
-const fsReadFile = Promise.denodeify(fs.readFile);
const fsWriteFile = Promise.denodeify(fs.writeFile);
const fsMkdir = Promise.denodeify(fs.mkdir);
@@ -67,7 +66,7 @@ describe('Acceptance: ng github-pages:deploy', function() {
});
});
- it('should deploy with defaults to existing remote', function() {
+ it('should deploy with defaults to existing remote', function () {
execStub.addExecSuccess('git status --porcelain')
.addExecSuccess('git rev-parse --abbrev-ref HEAD', initialBranch)
.addExecSuccess('git remote -v', remote)
@@ -78,12 +77,7 @@ describe('Acceptance: ng github-pages:deploy', function() {
.addExecSuccess(`git push origin ${ghPagesBranch}:${ghPagesBranch}`)
.addExecSuccess('git remote -v', remote);
- return ng(['github-pages:deploy', '--skip-build'])
- .then(() => {
- let indexHtml = path.join(process.cwd(), 'index.html');
- return fsReadFile(indexHtml, 'utf8');
- })
- .then((data) => expect(data.search(``)).to.not.equal(-1));
+ return ng(['github-pages:deploy', '--skip-build']);
});
it('should deploy with changed defaults', function() {
@@ -100,13 +94,7 @@ describe('Acceptance: ng github-pages:deploy', function() {
.addExecSuccess(`git push origin ${ghPagesBranch}:${userPageBranch}`)
.addExecSuccess('git remote -v', remote);
- return ng(['github-pages:deploy', '--skip-build', `--message=${message}`,
- '--user-page'])
- .then(() => {
- let indexHtml = path.join(process.cwd(), 'index.html');
- return fsReadFile(indexHtml, 'utf8');
- })
- .then((data) => expect(data.search('')).to.not.equal(-1));
+ return ng(['github-pages:deploy', '--skip-build', `--message=${message}`, '--user-page']);
});
it('should create branch if needed', function() {
@@ -125,12 +113,7 @@ describe('Acceptance: ng github-pages:deploy', function() {
.addExecSuccess(`git push origin ${ghPagesBranch}:${ghPagesBranch}`)
.addExecSuccess('git remote -v', remote);
- return ng(['github-pages:deploy', '--skip-build'])
- .then(() => {
- let indexHtml = path.join(process.cwd(), 'index.html');
- return fsReadFile(indexHtml, 'utf8');
- })
- .then((data) => expect(data.search(``)).to.not.equal(-1));
+ return ng(['github-pages:deploy', '--skip-build']);
});
it('should create repo if needed', function() {
@@ -183,11 +166,6 @@ describe('Acceptance: ng github-pages:deploy', function() {
return ng(['github-pages:deploy', '--skip-build', `--gh-token=${token}`,
`--gh-username=${username}`])
- .then(() => {
- let indexHtml = path.join(process.cwd(), 'index.html');
- return fsReadFile(indexHtml, 'utf8');
- })
- .then((data) => expect(data.search(``)).to.not.equal(-1))
.then(() => httpsStub.restore());
});
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(/