Skip to content

Commit f9e08c7

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

11 files changed

+128
-10
lines changed

README.md

+15
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ The generated project has dependencies that require **Node 4 or greater**.
2929
* [Generating a Route](#generating-a-route)
3030
* [Creating a Build](#creating-a-build)
3131
* [Build Targets and Environment Files](#build-targets-and-environment-files)
32+
* [Base tag handling in index.html](#base-tag-handling-in-indexhtml)
3233
* [Bundling](#bundling)
3334
* [Running Unit Tests](#running-unit-tests)
3435
* [Running End-to-End Tests](#running-end-to-end-tests)
@@ -141,6 +142,20 @@ You can also add your own env files other than `dev` and `prod` by creating a
141142
`src/app/environments/environment.{NAME}.ts` and use them by using the `--env=NAME`
142143
flag on the build/serve commands.
143144

145+
### Base tag handling in index.html
146+
147+
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.
148+
149+
```bash
150+
# Sets base tag href to /myUrl/ in your index.html
151+
ng build --base-href /myUrl/
152+
ng serve --base-href /myUrl/
153+
154+
# Does nothing
155+
ng build --base-href
156+
ng serve --base-href
157+
```
158+
144159
### Bundling
145160

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

addon/ng2/commands/build.ts

+6-2
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

+4-1
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

+5-1
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

+2-2
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

+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).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

+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).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

+1-1
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}/`);
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
@@ -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)