Skip to content

Commit 95353cc

Browse files
committed
feat: implement --base-href argument
Implement --base-href argument for build and serve commands Closes angular#1064
1 parent 67e70a0 commit 95353cc

11 files changed

+141
-20
lines changed

README.md

+19-5
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Prototype of a CLI for Angular 2 applications based on the [ember-cli](http://ww
1313

1414
This project is very much still a work in progress.
1515

16-
The CLI is now in beta.
16+
The CLI is now in beta.
1717
If you wish to collaborate while the project is still young, check out [our issue list](https://github.com/angular/angular-cli/issues).
1818

1919
## Prerequisites
@@ -114,12 +114,12 @@ The build artifacts will be stored in the `dist/` directory.
114114

115115
### Build Targets and Environment Files
116116

117-
A build can specify both a build target (`development` or `production`) and an
118-
environment file to be used with that build. By default, the development build
117+
A build can specify both a build target (`development` or `production`) and an
118+
environment file to be used with that build. By default, the development build
119119
target is used.
120120

121121
At build time, `src/app/environments/environment.ts` will be replaced by
122-
`src/app/environments/environment.{NAME}.ts` where `NAME` is the argument
122+
`src/app/environments/environment.{NAME}.ts` where `NAME` is the argument
123123
provided to the `--environment` flag.
124124

125125
These options also apply to the serve command. If you do not pass a value for `environment`,
@@ -141,6 +141,20 @@ You can also add your own env files other than `dev` and `prod` by creating a
141141
`src/app/environments/environment.{NAME}.ts` and use them by using the `--env=NAME`
142142
flag on the build/serve commands.
143143

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

146160
Builds created with the `-prod` flag via `ng build -prod` or `ng serve -prod` bundle
@@ -299,7 +313,7 @@ Running `ng init` will check for changes in all the auto-generated files created
299313

300314
Carefully read the diffs for each code file, and either accept the changes or incorporate them manually after `ng init` finishes.
301315

302-
**The main cause of errors after an update is failing to incorporate these updates into your code**.
316+
**The main cause of errors after an update is failing to incorporate these updates into your code**.
303317

304318
You can find more details about changes between versions in [CHANGELOG.md](https://github.com/angular/angular-cli/blob/master/CHANGELOG.md).
305319

addon/ng2/commands/build.ts

+7-3
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) {
@@ -32,7 +34,7 @@ module.exports = Command.extend({
3234
}
3335
if (commandOptions.target === 'production') {
3436
commandOptions.environment = 'prod';
35-
}
37+
}
3638
}
3739

3840
var project = this.project;
@@ -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+
}

tests/acceptance/base-href.spec.js

+50
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.only('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

+18-4
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,20 @@ 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+
// Check base tag before building with --base-href tag
132+
const indexHtmlBefore = fs.readFileSync(path.join(process.cwd(), 'dist/index.html'), 'utf-8');
133+
expect(indexHtmlBefore).to.match(/<base href="\/"/);
134+
135+
sh.exec(`${ngBin} build --base-href /myUrl/`);
136+
137+
// Check for base tag after build
138+
const indexHtmlAfter = fs.readFileSync(path.join(process.cwd(), 'dist/index.html'), 'utf-8');
139+
expect(indexHtmlAfter).to.match(/<base href="\/myUrl\/"/);
140+
});
141+
128142
it('Can run `ng build` in created project', function () {
129143
this.timeout(420000);
130144

@@ -338,8 +352,8 @@ describe('Basic end-to-end Workflow', function () {
338352
let lessFile = path.join(componentPath, lessFilename);
339353
let lessExample = '.outer {\n .inner { background: #fff; }\n }';
340354
let componentContents = fs.readFileSync(componentFile, 'utf8');
341-
342-
sh.mv(cssFile, lessFile);
355+
356+
sh.mv(cssFile, lessFile);
343357
fs.writeFileSync(lessFile, lessExample, 'utf8');
344358
fs.writeFileSync(componentFile, componentContents.replace(new RegExp(cssFilename, 'g'), lessFilename), 'utf8');
345359

@@ -365,8 +379,8 @@ describe('Basic end-to-end Workflow', function () {
365379
let stylusFile = path.join(componentPath, stylusFilename);
366380
let stylusExample = '.outer {\n .inner { background: #fff; }\n }';
367381
let componentContents = fs.readFileSync(componentFile, 'utf8');
368-
369-
sh.mv(cssFile, stylusFile);
382+
383+
sh.mv(cssFile, stylusFile);
370384
fs.writeFileSync(stylusFile, stylusExample, 'utf8');
371385
fs.writeFileSync(componentFile, componentContents.replace(new RegExp(cssFilename, 'g'), stylusFilename), 'utf8');
372386

0 commit comments

Comments
 (0)