Skip to content

Commit d494f64

Browse files
committed
feat: implement --base-href argument
Implement --base-href argument for build and github-pages:deploy commands Closes angular#1064
1 parent f9df8bb commit d494f64

10 files changed

+135
-9
lines changed

README.md

+11
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ The generated project has dependencies that require **Node 4.x.x and NPM 3.x.x**
3737
* [Generating a Route](#generating-a-route)
3838
* [Creating a Build](#creating-a-build)
3939
* [Build Targets and Environment Files](#build-targets-and-environment-files)
40+
* [Base tag handling in index.html](#base-tag-handling-in-indexhtml)
4041
* [Adding extra files to the build](#adding-extra-files-to-the-build)
4142
* [Running Unit Tests](#running-unit-tests)
4243
* [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
152153
- add `{ NAME: 'src/environments/environment.NAME.ts' }` to the the `apps[0].environments` object in `angular-cli.json`
153154
- use them by using the `--env=NAME` flag on the build/serve commands.
154155

156+
### Base tag handling in index.html
157+
158+
When building you can modify base tag (`<base href="/">`) in your index.html with `--base-href your-url` option.
159+
160+
```bash
161+
# Sets base tag href to /myUrl/ in your index.html
162+
ng build --base-href /myUrl/
163+
ng build --bh /myUrl/
164+
```
165+
155166
### Bundling
156167

157168
All builds make use of bundling, and using the `--prod` flag in `ng build --prod`

addon/ng2/commands/build.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface BuildOptions {
99
watch?: boolean;
1010
watcher?: string;
1111
supressSizes: boolean;
12+
baseHref?: string;
1213
}
1314

1415
module.exports = Command.extend({
@@ -22,7 +23,8 @@ module.exports = Command.extend({
2223
{ name: 'output-path', type: 'Path', default: 'dist/', aliases: ['o'] },
2324
{ name: 'watch', type: Boolean, default: false, aliases: ['w'] },
2425
{ name: 'watcher', type: String },
25-
{ name: 'suppress-sizes', type: Boolean, default: false }
26+
{ name: 'suppress-sizes', type: Boolean, default: false },
27+
{ name: 'base-href', type: String, default: null, aliases: ['bh'] },
2628
],
2729

2830
run: function (commandOptions: BuildOptions) {

addon/ng2/commands/github-pages-deploy.ts

+19-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ const fsWriteFile = Promise.denodeify(fs.writeFile);
1515
const fsReadDir = Promise.denodeify(fs.readdir);
1616
const fsCopy = Promise.denodeify(fse.copy);
1717

18+
interface GithubPagesDeployOptions {
19+
message?: string;
20+
target?: string;
21+
environment?: string;
22+
userPage?: boolean;
23+
skipBuild?: boolean;
24+
ghToken?: string;
25+
ghUsername?: string;
26+
baseHref?: string;
27+
}
28+
1829
module.exports = Command.extend({
1930
name: 'github-pages:deploy',
2031
aliases: ['gh-pages:deploy'],
@@ -57,9 +68,14 @@ module.exports = Command.extend({
5768
type: String,
5869
default: '',
5970
description: 'Github username'
71+
}, {
72+
name: 'base-href',
73+
type: String,
74+
default: null,
75+
aliases: ['bh']
6076
}],
6177

62-
run: function(options, rawArgs) {
78+
run: function(options: GithubPagesDeployOptions, rawArgs) {
6379
var ui = this.ui;
6480
var root = this.project.root;
6581
var execOptions = {
@@ -98,7 +114,8 @@ module.exports = Command.extend({
98114
var buildOptions = {
99115
target: options.target,
100116
environment: options.environment,
101-
outputPath: outDir
117+
outputPath: outDir,
118+
baseHref: options.baseHref,
102119
};
103120

104121
var createGithubRepoTask = new CreateGithubRepo({

addon/ng2/models/webpack-build-common.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import * as webpack from 'webpack';
55
import * as atl from 'awesome-typescript-loader';
66

77
import {findLazyModules} from './find-lazy-modules';
8+
import { BaseHrefWebpackPlugin } from '../utilities/base-href-webpack-plugin';
89

9-
export function getWebpackCommonConfig(projectRoot: string, environment: string, appConfig: any) {
10+
export function getWebpackCommonConfig(projectRoot: string, environment: string, appConfig: any, baseHref: string) {
1011

1112
const appRoot = path.resolve(projectRoot, appConfig.root);
1213
const appMain = path.resolve(appRoot, appConfig.main);
@@ -92,6 +93,9 @@ export function getWebpackCommonConfig(projectRoot: string, environment: string,
9293
template: path.resolve(appRoot, appConfig.index),
9394
chunksSortMode: 'dependency'
9495
}),
96+
new BaseHrefWebpackPlugin({
97+
baseHref: baseHref
98+
}),
9599
new webpack.NormalModuleReplacementPlugin(
96100
// This plugin is responsible for swapping the environment files.
97101
// Since it takes a RegExp as first parameter, we need to escape the path.

addon/ng2/models/webpack-config.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ export class NgCliWebpackConfig {
1818
private webpackMobileConfigPartial: any;
1919
private webpackMobileProdConfigPartial: any;
2020

21-
constructor(public ngCliProject: any, public target: string, public environment: string, outputDir?: string) {
21+
constructor(public ngCliProject: any, public target: string, public environment: string, outputDir?: string, public baseHref?: string) {
2222
const config: CliConfig = CliConfig.fromProject();
2323
const appConfig = config.config.apps[0];
2424

2525
appConfig.outDir = outputDir || appConfig.outDir;
2626

27-
this.webpackBaseConfig = getWebpackCommonConfig(this.ngCliProject.root, environment, appConfig);
27+
this.webpackBaseConfig = getWebpackCommonConfig(this.ngCliProject.root, environment, appConfig, baseHref);
2828
this.webpackDevConfigPartial = getWebpackDevConfigPartial(this.ngCliProject.root, appConfig);
2929
this.webpackProdConfigPartial = getWebpackProdConfigPartial(this.ngCliProject.root, appConfig);
3030

addon/ng2/tasks/build-webpack-watch.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ module.exports = Task.extend({
1616

1717
rimraf.sync(path.resolve(project.root, runTaskOptions.outputPath));
1818

19-
const config = new NgCliWebpackConfig(project, runTaskOptions.target, runTaskOptions.environment, runTaskOptions.outputPath).config;
19+
const config = new NgCliWebpackConfig(project, runTaskOptions.target, runTaskOptions.environment, runTaskOptions.outputPath, runTaskOptions.baseHref).config;
2020
const webpackCompiler = webpack(config);
2121

2222
webpackCompiler.apply(new ProgressPlugin({

addon/ng2/tasks/build-webpack.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ let lastHash: any = null;
1111

1212
module.exports = Task.extend({
1313
// Options: String outputPath
14-
run: function(runTaskOptions: ServeTaskOptions) {
14+
run: function (runTaskOptions: ServeTaskOptions) {
1515

1616
var project = this.cliProject;
1717

1818
rimraf.sync(path.resolve(project.root, runTaskOptions.outputPath));
19-
var config = new NgCliWebpackConfig(project, runTaskOptions.target, runTaskOptions.environment, runTaskOptions.outputPath).config;
19+
var config = new NgCliWebpackConfig(project, runTaskOptions.target, runTaskOptions.environment, runTaskOptions.outputPath, runTaskOptions.baseHref).config;
2020

2121
const webpackCompiler = webpack(config);
2222

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
interface BaseHrefWebpackPluginOptions {
2+
baseHref: string;
3+
}
4+
5+
export class BaseHrefWebpackPlugin {
6+
constructor(private options: BaseHrefWebpackPluginOptions) { }
7+
8+
apply(compiler): void {
9+
// Ignore if baseHref is not passed
10+
if (!this.options.baseHref) {
11+
return;
12+
}
13+
14+
compiler.plugin('compilation', (compilation) => {
15+
compilation.plugin('html-webpack-plugin-before-html-processing', (htmlPluginData, callback) => {
16+
// Check if base tag already exists
17+
const baseTagRegex = /<base.*?>/i;
18+
const baseTagMatches = htmlPluginData.html.match(baseTagRegex);
19+
if (!baseTagMatches) {
20+
// Insert it in top of the head if not exist
21+
htmlPluginData.html = htmlPluginData.html.replace(/<head>/i, '$&' + `<base href="${this.options.baseHref}">`);
22+
} else {
23+
// Replace only href attribute if exists
24+
const modifiedBaseTag = baseTagMatches[0].replace(/href="\S+"/i, `href="${this.options.baseHref}"`);
25+
htmlPluginData.html = htmlPluginData.html.replace(baseTagRegex, modifiedBaseTag);
26+
}
27+
28+
callback(null, htmlPluginData);
29+
});
30+
});
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*eslint-disable no-console */
2+
'use strict';
3+
4+
var expect = require('chai').expect;
5+
var BaseHrefWebpackPlugin = require('../../addon/ng2/utilities/base-href-webpack-plugin').BaseHrefWebpackPlugin;
6+
7+
function mockCompiler(indexHtml, callback) {
8+
return {
9+
plugin: function (event, compilerCallback) {
10+
var compilation = {
11+
plugin: function (hook, compilationCallback) {
12+
var htmlPluginData = {
13+
html: indexHtml
14+
};
15+
compilationCallback(htmlPluginData, callback);
16+
}
17+
};
18+
compilerCallback(compilation);
19+
}
20+
};
21+
}
22+
23+
describe('base href webpack plugin', function () {
24+
it('should do nothing when baseHref is null', function () {
25+
var plugin = new BaseHrefWebpackPlugin({ baseHref: null });
26+
27+
var compiler = mockCompiler('<body><head></head></body>', function (x, htmlPluginData) {
28+
expect(htmlPluginData.html).to.equal('<body><head></head></body>');
29+
});
30+
plugin.apply(compiler);
31+
});
32+
33+
it('should insert base tag when not exist', function () {
34+
var plugin = new BaseHrefWebpackPlugin({ baseHref: '/' });
35+
36+
var compiler = mockCompiler('<body><head></head></body>', function (x, htmlPluginData) {
37+
expect(htmlPluginData.html).to.equal('<body><head><base href="/"></head></body>');
38+
});
39+
plugin.apply(compiler);
40+
});
41+
42+
it('should replace href attribute when base tag already exists', function () {
43+
var plugin = new BaseHrefWebpackPlugin({ baseHref: '/myUrl/' });
44+
45+
var compiler = mockCompiler('<body><head><base href="/" target="_blank"></head></body>', function (x, htmlPluginData) {
46+
expect(htmlPluginData.html).to.equal('<body><head><base href="/myUrl/" target="_blank"></head></body>');
47+
});
48+
plugin.apply(compiler);
49+
});
50+
});

tests/e2e/e2e_workflow.spec.js

+10
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,16 @@ describe('Basic end-to-end Workflow', function () {
128128
});
129129
});
130130

131+
it('Supports base tag modifications via `ng build --base-href`', function() {
132+
this.timeout(420000);
133+
134+
sh.exec(`${ngBin} build --base-href /myUrl/`);
135+
const indexHtmlPath = path.join(process.cwd(), 'dist/index.html');
136+
const indexHtml = fs.readFileSync(indexHtmlPath, { encoding: 'utf8' });
137+
138+
expect(indexHtml).to.match(/<base href="\/myUrl\/"/);
139+
});
140+
131141
it('Can run `ng build` in created project', function () {
132142
this.timeout(420000);
133143

0 commit comments

Comments
 (0)