Skip to content

Commit b216b06

Browse files
committed
feat: implement --base-href argument
Implement --base-href argument for build and serve commands Closes angular#1064
1 parent 560ae8f commit b216b06

11 files changed

+124
-10
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ The generated project has dependencies that require **Node 4 or greater**.
3535
* [Generating a Route](#generating-a-route)
3636
* [Creating a Build](#creating-a-build)
3737
* [Build Targets and Environment Files](#build-targets-and-environment-files)
38+
* [Base tag handling in index.html](#base-tag-handling-in-indexhtml)
3839
* [Bundling](#bundling)
3940
* [Running Unit Tests](#running-unit-tests)
4041
* [Running End-to-End Tests](#running-end-to-end-tests)
@@ -147,6 +148,16 @@ You can also add your own env files other than `dev` and `prod` by creating a
147148
`src/app/environments/environment.{NAME}.ts` and use them by using the `--env=NAME`
148149
flag on the build/serve commands.
149150

151+
### Base tag handling in index.html
152+
153+
You can modify base tag (`<base href="/">`) in your index.html by using `--base-href your-url` option. It's useful when building or serving for different environments.
154+
155+
```bash
156+
# Sets base tag href to /myUrl/ in your index.html
157+
ng build --base-href /myUrl/
158+
ng serve --base-href /myUrl/
159+
```
160+
150161
### Bundling
151162

152163
Builds created with the `-prod` flag via `ng build -prod` or `ng serve -prod` bundle

addon/ng2/commands/build.ts

Lines changed: 6 additions & 2 deletions
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 },
2628
],
2729

2830
run: function (commandOptions: BuildOptions) {
@@ -43,14 +45,16 @@ module.exports = Command.extend({
4345
ui: ui,
4446
outputPath: commandOptions.outputPath,
4547
target: commandOptions.target,
46-
environment: commandOptions.environment
48+
environment: commandOptions.environment,
49+
baseHref: commandOptions.baseHref,
4750
}) :
4851
new WebpackBuild({
4952
cliProject: project,
5053
ui: ui,
5154
outputPath: commandOptions.outputPath,
5255
target: commandOptions.target,
5356
environment: commandOptions.environment,
57+
baseHref: commandOptions.baseHref,
5458
});
5559

5660
return buildTask.run(commandOptions);

addon/ng2/commands/serve.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export interface ServeTaskOptions {
2828
ssl?: boolean;
2929
sslKey?: string;
3030
sslCert?: string;
31+
baseHref?: string;
3132
}
3233

3334
module.exports = Command.extend({
@@ -51,7 +52,8 @@ module.exports = Command.extend({
5152
{ name: 'output-path', type: 'Path', default: 'dist/', aliases: ['op', 'out'] },
5253
{ name: 'ssl', type: Boolean, default: false },
5354
{ name: 'ssl-key', type: String, default: 'ssl/server.key' },
54-
{ name: 'ssl-cert', type: String, default: 'ssl/server.crt' }
55+
{ name: 'ssl-cert', type: String, default: 'ssl/server.crt' },
56+
{ name: 'base-href', type: String, default: null },
5557
],
5658

5759
run: function(commandOptions: ServeTaskOptions) {
@@ -85,6 +87,7 @@ module.exports = Command.extend({
8587
ui: this.ui,
8688
analytics: this.analytics,
8789
project: this.project,
90+
baseHref: commandOptions.baseHref,
8891
});
8992

9093
return serve.run(commandOptions);

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import * as HtmlWebpackPlugin from 'html-webpack-plugin';
44
import * as webpack from 'webpack';
55
import { ForkCheckerPlugin } from 'awesome-typescript-loader';
66
import { CliConfig } from './config';
7+
import { BaseHrefWebpackPlugin } from '../utilities/base-href-webpack-plugin';
78

8-
export function getWebpackCommonConfig(projectRoot: string, sourceDir: string) {
9+
export function getWebpackCommonConfig(projectRoot: string, sourceDir: string, baseHref: string) {
910
return {
1011
devtool: 'inline-source-map',
1112
resolve: {
@@ -64,6 +65,9 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string) {
6465
template: path.resolve(projectRoot, `./${sourceDir}/index.html`),
6566
chunksSortMode: 'dependency'
6667
}),
68+
new BaseHrefWebpackPlugin({
69+
baseHref: baseHref
70+
}),
6771
new webpack.optimize.CommonsChunkPlugin({
6872
name: ['polyfills']
6973
}),

addon/ng2/models/webpack-config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ export class NgCliWebpackConfig {
2323
private webpackMobileConfigPartial: any;
2424
private webpackMobileProdConfigPartial: any;
2525

26-
constructor(public ngCliProject: any, public target: string, public environment: string) {
26+
constructor(public ngCliProject: any, public target: string, public environment: string, public baseHref: string) {
2727
const sourceDir = CliConfig.fromProject().defaults.sourceDir;
2828

2929
const environmentPath = `./${sourceDir}/app/environments/environment.${environment}.ts`;
3030

31-
this.webpackBaseConfig = getWebpackCommonConfig(this.ngCliProject.root, sourceDir);
31+
this.webpackBaseConfig = getWebpackCommonConfig(this.ngCliProject.root, sourceDir, baseHref);
3232
this.webpackDevConfigPartial = getWebpackDevConfigPartial(this.ngCliProject.root, sourceDir);
3333
this.webpackProdConfigPartial = getWebpackProdConfigPartial(this.ngCliProject.root, sourceDir);
3434

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

Lines changed: 1 addition & 1 deletion
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).config;
19+
const config = new NgCliWebpackConfig(project, runTaskOptions.target, runTaskOptions.environment, runTaskOptions.baseHref).config;
2020
const webpackCompiler = webpack(config);
2121

2222
webpackCompiler.apply(new ProgressPlugin({

addon/ng2/tasks/build-webpack.ts

Lines changed: 2 additions & 2 deletions
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).config;
19+
var config = new NgCliWebpackConfig(project, runTaskOptions.target, runTaskOptions.environment, runTaskOptions.baseHref).config;
2020
const webpackCompiler = webpack(config);
2121

2222
const ProgressPlugin = require('webpack/lib/ProgressPlugin');

addon/ng2/tasks/serve-webpack.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ module.exports = Task.extend({
1515
let lastHash = null;
1616
let webpackCompiler: any;
1717

18-
var config: NgCliWebpackConfig = new NgCliWebpackConfig(this.project, commandOptions.target, commandOptions.environment).config;
18+
var config: NgCliWebpackConfig = new NgCliWebpackConfig(this.project, commandOptions.target, commandOptions.environment, commandOptions.baseHref).config;
1919
// This allows for live reload of page when changes are made to repo.
2020
// https://webpack.github.io/docs/webpack-dev-server.html#inline-mode
2121
config.entry.main.unshift(`webpack-dev-server/client?http://${commandOptions.host}:${commandOptions.port}/`);
Lines changed: 32 additions & 0 deletions
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+
}
Lines changed: 50 additions & 0 deletions
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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,16 @@ describe('Basic end-to-end Workflow', function () {
125125
});
126126
});
127127

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

0 commit comments

Comments
 (0)