Skip to content

Commit 74b29b3

Browse files
dzonatanfilipesilva
authored andcommitted
feat(build): implement --base-href argument
Implement --base-href argument for build and github-pages:deploy commands Closes #1064 Closes #1506
1 parent f03f275 commit 74b29b3

11 files changed

+176
-51
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

+4-2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ import * as Command from 'ember-cli/lib/models/command';
22
import * as WebpackBuild from '../tasks/build-webpack';
33
import * as WebpackBuildWatch from '../tasks/build-webpack-watch';
44

5-
interface BuildOptions {
5+
export interface BuildOptions {
66
target?: string;
77
environment?: string;
88
outputPath?: string;
99
watch?: boolean;
1010
watcher?: string;
1111
supressSizes: boolean;
12+
baseHref?: string;
1213
}
1314

1415
module.exports = Command.extend({
@@ -27,7 +28,8 @@ module.exports = Command.extend({
2728
{ name: 'output-path', type: 'Path', default: 'dist/', aliases: ['o'] },
2829
{ name: 'watch', type: Boolean, default: false, aliases: ['w'] },
2930
{ name: 'watcher', type: String },
30-
{ name: 'suppress-sizes', type: Boolean, default: false }
31+
{ name: 'suppress-sizes', type: Boolean, default: false },
32+
{ name: 'base-href', type: String, default: null, aliases: ['bh'] },
3133
],
3234

3335
run: function (commandOptions: BuildOptions) {

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

+32-14
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,20 @@ import * as CreateGithubRepo from '../tasks/create-github-repo';
1111
import { CliConfig } from '../models/config';
1212
import { oneLine } from 'common-tags';
1313

14-
const fsReadFile = Promise.denodeify(fs.readFile);
15-
const fsWriteFile = Promise.denodeify(fs.writeFile);
1614
const fsReadDir = Promise.denodeify(fs.readdir);
1715
const fsCopy = Promise.denodeify(fse.copy);
1816

17+
interface GithubPagesDeployOptions {
18+
message?: string;
19+
target?: string;
20+
environment?: string;
21+
userPage?: boolean;
22+
skipBuild?: boolean;
23+
ghToken?: string;
24+
ghUsername?: string;
25+
baseHref?: string;
26+
}
27+
1928
module.exports = Command.extend({
2029
name: 'github-pages:deploy',
2130
aliases: ['gh-pages:deploy'],
@@ -61,9 +70,14 @@ module.exports = Command.extend({
6170
type: String,
6271
default: '',
6372
description: 'Github username'
73+
}, {
74+
name: 'base-href',
75+
type: String,
76+
default: null,
77+
aliases: ['bh']
6478
}],
6579

66-
run: function(options, rawArgs) {
80+
run: function(options: GithubPagesDeployOptions, rawArgs) {
6781
const ui = this.ui;
6882
const root = this.project.root;
6983
const execOptions = {
@@ -99,10 +113,19 @@ module.exports = Command.extend({
99113
outputPath: outDir
100114
});
101115

116+
/**
117+
* BaseHref tag setting logic:
118+
* First, use --base-href flag value if provided.
119+
* Else if --user-page is true, then keep baseHref default as declared in index.html.
120+
* Otherwise auto-replace with `/${projectName}/`.
121+
*/
122+
const baseHref = options.baseHref || (options.userPage ? null : `/${projectName}/`);
123+
102124
const buildOptions = {
103125
target: options.target,
104126
environment: options.environment,
105-
outputPath: outDir
127+
outputPath: outDir,
128+
baseHref: baseHref,
106129
};
107130

108131
const createGithubRepoTask = new CreateGithubRepo({
@@ -123,7 +146,7 @@ module.exports = Command.extend({
123146
.then(createGitHubRepoIfNeeded)
124147
.then(checkoutGhPages)
125148
.then(copyFiles)
126-
.then(updateBaseHref)
149+
.then(createNotFoundPage)
127150
.then(addAndCommit)
128151
.then(returnStartingBranch)
129152
.then(pushToGitRepo)
@@ -191,15 +214,10 @@ module.exports = Command.extend({
191214
})));
192215
}
193216

194-
function updateBaseHref() {
195-
if (options.userPage) { return Promise.resolve(); }
196-
let indexHtml = path.join(root, 'index.html');
197-
return fsReadFile(indexHtml, 'utf8')
198-
.then((data) => data.replace(/<base href="\/">/g, `<base href="/${projectName}/">`))
199-
.then((data) => {
200-
fsWriteFile(indexHtml, data, 'utf8');
201-
fsWriteFile(path.join(root, '404.html'), data, 'utf8');
202-
});
217+
function createNotFoundPage() {
218+
const indexHtml = path.join(root, 'index.html');
219+
const notFoundPage = path.join(root, '404.html');
220+
return fsCopy(indexHtml, notFoundPage);
203221
}
204222

205223
function addAndCommit() {

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@ 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(
11+
projectRoot: string,
12+
environment: string,
13+
appConfig: any,
14+
baseHref: string
15+
) {
1016

1117
const appRoot = path.resolve(projectRoot, appConfig.root);
1218
const appMain = path.resolve(appRoot, appConfig.main);
@@ -118,6 +124,9 @@ export function getWebpackCommonConfig(projectRoot: string, environment: string,
118124
template: path.resolve(appRoot, appConfig.index),
119125
chunksSortMode: 'dependency'
120126
}),
127+
new BaseHrefWebpackPlugin({
128+
baseHref: baseHref
129+
}),
121130
new webpack.NormalModuleReplacementPlugin(
122131
// This plugin is responsible for swapping the environment files.
123132
// Since it takes a RegExp as first parameter, we need to escape the path.

addon/ng2/models/webpack-config.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,20 @@ export class NgCliWebpackConfig {
2020
public ngCliProject: any,
2121
public target: string,
2222
public environment: string,
23-
outputDir?: string
23+
outputDir?: string,
24+
baseHref?: string
2425
) {
2526
const config: CliConfig = CliConfig.fromProject();
2627
const appConfig = config.config.apps[0];
2728

2829
appConfig.outDir = outputDir || appConfig.outDir;
2930

30-
this.baseConfig = getWebpackCommonConfig(this.ngCliProject.root, environment, appConfig);
31+
this.baseConfig = getWebpackCommonConfig(
32+
this.ngCliProject.root,
33+
environment,
34+
appConfig,
35+
baseHref
36+
);
3137
this.devConfigPartial = getWebpackDevConfigPartial(this.ngCliProject.root, appConfig);
3238
this.prodConfigPartial = getWebpackProdConfigPartial(this.ngCliProject.root, appConfig);
3339

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import * as webpack from 'webpack';
55
import * as ProgressPlugin from 'webpack/lib/ProgressPlugin';
66
import { NgCliWebpackConfig } from '../models/webpack-config';
77
import { webpackOutputOptions } from '../models/';
8-
import { ServeTaskOptions } from '../commands/serve';
8+
import { BuildOptions } from '../commands/build';
99

1010
let lastHash: any = null;
1111

1212
module.exports = Task.extend({
13-
run: function(runTaskOptions: ServeTaskOptions) {
13+
run: function(runTaskOptions: BuildOptions) {
1414

1515
const project = this.cliProject;
1616

@@ -20,7 +20,8 @@ module.exports = Task.extend({
2020
project,
2121
runTaskOptions.target,
2222
runTaskOptions.environment,
23-
runTaskOptions.outputPath
23+
runTaskOptions.outputPath,
24+
runTaskOptions.baseHref
2425
).config;
2526
const webpackCompiler = webpack(config);
2627

addon/ng2/tasks/build-webpack.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as rimraf from 'rimraf';
22
import * as path from 'path';
33
import * as Task from 'ember-cli/lib/models/task';
44
import * as webpack from 'webpack';
5-
import { ServeTaskOptions } from '../commands/serve';
5+
import { BuildOptions } from '../commands/build';
66
import { NgCliWebpackConfig } from '../models/webpack-config';
77
import { webpackOutputOptions } from '../models/';
88

@@ -11,7 +11,7 @@ let lastHash: any = null;
1111

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

1616
const project = this.cliProject;
1717

@@ -20,7 +20,8 @@ module.exports = Task.extend({
2020
project,
2121
runTaskOptions.target,
2222
runTaskOptions.environment,
23-
runTaskOptions.outputPath
23+
runTaskOptions.outputPath,
24+
runTaskOptions.baseHref
2425
).config;
2526

2627
const webpackCompiler = webpack(config);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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(
16+
'html-webpack-plugin-before-html-processing',
17+
(htmlPluginData, callback) => {
18+
// Check if base tag already exists
19+
const baseTagRegex = /<base.*?>/i;
20+
const baseTagMatches = htmlPluginData.html.match(baseTagRegex);
21+
if (!baseTagMatches) {
22+
// Insert it in top of the head if not exist
23+
htmlPluginData.html = htmlPluginData.html.replace(
24+
/<head>/i, '$&' + `<base href="${this.options.baseHref}">`
25+
);
26+
} else {
27+
// Replace only href attribute if exists
28+
const modifiedBaseTag = baseTagMatches[0].replace(
29+
/href="\S+"/i, `href="${this.options.baseHref}"`
30+
);
31+
htmlPluginData.html = htmlPluginData.html.replace(baseTagRegex, modifiedBaseTag);
32+
}
33+
34+
callback(null, htmlPluginData);
35+
}
36+
);
37+
});
38+
}
39+
}
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+
});

0 commit comments

Comments
 (0)