Skip to content

Commit

Permalink
feat: implement --base-href argument
Browse files Browse the repository at this point in the history
Implement --base-href argument for build and serve commands

Closes angular#1064
  • Loading branch information
dzonatan committed Jul 31, 2016
1 parent 67e70a0 commit a89cde0
Show file tree
Hide file tree
Showing 11 changed files with 133 additions and 12 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,20 @@ You can also add your own env files other than `dev` and `prod` by creating a
`src/app/environments/environment.{NAME}.ts` and use them by using the `--env=NAME`
flag on the build/serve commands.

### Base tag handling in index.html

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.

```bash
# Sets base tag href to /myUrl/ in your index.html
ng build --base-href /myUrl/
ng serve --base-href /myUrl/

# Does nothing
ng build --base-href
ng serve --base-href
```

### Bundling

Builds created with the `-prod` flag via `ng build -prod` or `ng serve -prod` bundle
Expand Down
8 changes: 6 additions & 2 deletions addon/ng2/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface BuildOptions {
watch?: boolean;
watcher?: string;
supressSizes: boolean;
baseHref?: string;
}

module.exports = Command.extend({
Expand All @@ -22,7 +23,8 @@ module.exports = Command.extend({
{ name: 'output-path', type: 'Path', default: 'dist/', aliases: ['o'] },
{ name: 'watch', type: Boolean, default: false, aliases: ['w'] },
{ name: 'watcher', type: String },
{ name: 'suppress-sizes', type: Boolean, default: false }
{ name: 'suppress-sizes', type: Boolean, default: false },
{ name: 'base-href', type: String, default: null },
],

run: function (commandOptions: BuildOptions) {
Expand All @@ -43,14 +45,16 @@ module.exports = Command.extend({
ui: ui,
outputPath: commandOptions.outputPath,
target: commandOptions.target,
environment: commandOptions.environment
environment: commandOptions.environment,
baseHref: commandOptions.baseHref
}) :
new WebpackBuild({
cliProject: project,
ui: ui,
outputPath: commandOptions.outputPath,
target: commandOptions.target,
environment: commandOptions.environment,
baseHref: commandOptions.baseHref
});

return buildTask.run(commandOptions);
Expand Down
5 changes: 4 additions & 1 deletion addon/ng2/commands/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface ServeTaskOptions {
ssl?: boolean;
sslKey?: string;
sslCert?: string;
baseHref?: string;
}

module.exports = Command.extend({
Expand All @@ -51,7 +52,8 @@ module.exports = Command.extend({
{ name: 'output-path', type: 'Path', default: 'dist/', aliases: ['op', 'out'] },
{ name: 'ssl', type: Boolean, default: false },
{ name: 'ssl-key', type: String, default: 'ssl/server.key' },
{ name: 'ssl-cert', type: String, default: 'ssl/server.crt' }
{ name: 'ssl-cert', type: String, default: 'ssl/server.crt' },
{ name: 'base-href', type: String, default: null },
],

run: function(commandOptions: ServeTaskOptions) {
Expand Down Expand Up @@ -85,6 +87,7 @@ module.exports = Command.extend({
ui: this.ui,
analytics: this.analytics,
project: this.project,
baseHref: commandOptions.baseHref
});

return serve.run(commandOptions);
Expand Down
6 changes: 5 additions & 1 deletion addon/ng2/models/webpack-build-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import * as HtmlWebpackPlugin from 'html-webpack-plugin';
import * as webpack from 'webpack';
import { ForkCheckerPlugin } from 'awesome-typescript-loader';
import { CliConfig } from './config';
import { BaseHrefWebpackPlugin } from '../utilities/base-href-webpack-plugin';

export function getWebpackCommonConfig(projectRoot: string, sourceDir: string) {
export function getWebpackCommonConfig(projectRoot: string, sourceDir: string, baseHref: string) {
return {
devtool: 'inline-source-map',
resolve: {
Expand Down Expand Up @@ -64,6 +65,9 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string) {
template: path.resolve(projectRoot, `./${sourceDir}/index.html`),
chunksSortMode: 'dependency'
}),
new BaseHrefWebpackPlugin({
baseHref: baseHref
}),
new webpack.optimize.CommonsChunkPlugin({
name: ['polyfills']
}),
Expand Down
4 changes: 2 additions & 2 deletions addon/ng2/models/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ export class NgCliWebpackConfig {
private webpackMobileConfigPartial: any;
private webpackMobileProdConfigPartial: any;

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

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

this.webpackBaseConfig = getWebpackCommonConfig(this.ngCliProject.root, sourceDir);
this.webpackBaseConfig = getWebpackCommonConfig(this.ngCliProject.root, sourceDir, baseHref);
this.webpackDevConfigPartial = getWebpackDevConfigPartial(this.ngCliProject.root, sourceDir);
this.webpackProdConfigPartial = getWebpackProdConfigPartial(this.ngCliProject.root, sourceDir);

Expand Down
2 changes: 1 addition & 1 deletion addon/ng2/tasks/build-webpack-watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module.exports = Task.extend({

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

const config = new NgCliWebpackConfig(project, runTaskOptions.target, runTaskOptions.environment).config;
const config = new NgCliWebpackConfig(project, runTaskOptions.target, runTaskOptions.environment, runTaskOptions.baseHref).config;
const webpackCompiler = webpack(config);

webpackCompiler.apply(new ProgressPlugin({
Expand Down
4 changes: 2 additions & 2 deletions addon/ng2/tasks/build-webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ let lastHash: any = null;

module.exports = Task.extend({
// Options: String outputPath
run: function(runTaskOptions: ServeTaskOptions) {
run: function (runTaskOptions: ServeTaskOptions) {

var project = this.cliProject;

rimraf.sync(path.resolve(project.root, runTaskOptions.outputPath));
var config = new NgCliWebpackConfig(project, runTaskOptions.target, runTaskOptions.environment).config;
var config = new NgCliWebpackConfig(project, runTaskOptions.target, runTaskOptions.environment, runTaskOptions.baseHref).config;
const webpackCompiler = webpack(config);

const ProgressPlugin = require('webpack/lib/ProgressPlugin');
Expand Down
2 changes: 1 addition & 1 deletion addon/ng2/tasks/serve-webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module.exports = Task.extend({
let lastHash = null;
let webpackCompiler: any;

var config: NgCliWebpackConfig = new NgCliWebpackConfig(this.project, commandOptions.target, commandOptions.environment).config;
var config: NgCliWebpackConfig = new NgCliWebpackConfig(this.project, commandOptions.target, commandOptions.environment, commandOptions.baseHref).config;
// This allows for live reload of page when changes are made to repo.
// https://webpack.github.io/docs/webpack-dev-server.html#inline-mode
config.entry.main.unshift(`webpack-dev-server/client?http://${commandOptions.host}:${commandOptions.port}/`);
Expand Down
32 changes: 32 additions & 0 deletions addon/ng2/utilities/base-href-webpack-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
interface BaseHrefWebpackPluginOptions {
baseHref: string;
}

export class BaseHrefWebpackPlugin {
constructor(private options: BaseHrefWebpackPluginOptions) { }

apply(compiler): void {
// Ignore if baseHref is not passed
if (!this.options.baseHref) {
return;
}

compiler.plugin('compilation', (compilation) => {
compilation.plugin('html-webpack-plugin-before-html-processing', (htmlPluginData, callback) => {
// Check if base tag already exists
const baseTagRegex = /<base.*?>/i;
const baseTagMatches = htmlPluginData.html.match(baseTagRegex);
if (!baseTagMatches) {
// Insert it in top of the head if not exist
htmlPluginData.html = htmlPluginData.html.replace(/<head>/i, '$&' + `<base href="${this.options.baseHref}">`);
} else {
// Replace only href attribute if exists
const modifiedBaseTag = baseTagMatches[0].replace(/href="\S+"/i, `href="${this.options.baseHref}"`);
htmlPluginData.html = htmlPluginData.html.replace(baseTagRegex, modifiedBaseTag);
}

callback(null, htmlPluginData);
});
});
}
}
50 changes: 50 additions & 0 deletions tests/acceptance/base-href-webpack-plugin.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*eslint-disable no-console */
'use strict';

var expect = require('chai').expect;
var BaseHrefWebpackPlugin = require('../../addon/ng2/utilities/base-href-webpack-plugin').BaseHrefWebpackPlugin;

function mockCompiler(indexHtml, callback) {
return {
plugin: function (event, compilerCallback) {
var compilation = {
plugin: function (hook, compilationCallback) {
var htmlPluginData = {
html: indexHtml
};
compilationCallback(htmlPluginData, callback);
}
};
compilerCallback(compilation);
}
};
}

describe('base href webpack plugin', function () {
it('should do nothing when baseHref is null', function () {
var plugin = new BaseHrefWebpackPlugin({ baseHref: null });

var compiler = mockCompiler('<body><head></head></body>', function (x, htmlPluginData) {
expect(htmlPluginData.html).to.equal('<body><head></head></body>');
});
plugin.apply(compiler);
});

it('should insert base tag when not exist', function () {
var plugin = new BaseHrefWebpackPlugin({ baseHref: '/' });

var compiler = mockCompiler('<body><head></head></body>', function (x, htmlPluginData) {
expect(htmlPluginData.html).to.equal('<body><head><base href="/"></head></body>');
});
plugin.apply(compiler);
});

it('should replace href attribute when base tag already exists', function () {
var plugin = new BaseHrefWebpackPlugin({ baseHref: '/myUrl/' });

var compiler = mockCompiler('<body><head><base href="/" target="_blank"></head></body>', function (x, htmlPluginData) {
expect(htmlPluginData.html).to.equal('<body><head><base href="/myUrl/" target="_blank"></head></body>');
});
plugin.apply(compiler);
});
});
18 changes: 16 additions & 2 deletions tests/e2e/e2e_workflow.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,20 @@ describe('Basic end-to-end Workflow', function () {
});
});

it('Supports base tag modifications via `ng build --base-href`', function() {
this.timeout(420000);

// Check base tag before building with --base-href tag
const indexHtmlBefore = fs.readFileSync(path.join(process.cwd(), 'dist/index.html'), 'utf-8');
expect(indexHtmlBefore).to.match(/<base href="\/"/);

sh.exec(`${ngBin} build --base-href /myUrl/`);

// Check for base tag after build
const indexHtmlAfter = fs.readFileSync(path.join(process.cwd(), 'dist/index.html'), 'utf-8');
expect(indexHtmlAfter).to.match(/<base href="\/myUrl\/"/);
});

it('Can run `ng build` in created project', function () {
this.timeout(420000);

Expand Down Expand Up @@ -339,7 +353,7 @@ describe('Basic end-to-end Workflow', function () {
let lessExample = '.outer {\n .inner { background: #fff; }\n }';
let componentContents = fs.readFileSync(componentFile, 'utf8');

sh.mv(cssFile, lessFile);
sh.mv(cssFile, lessFile);
fs.writeFileSync(lessFile, lessExample, 'utf8');
fs.writeFileSync(componentFile, componentContents.replace(new RegExp(cssFilename, 'g'), lessFilename), 'utf8');

Expand All @@ -366,7 +380,7 @@ describe('Basic end-to-end Workflow', function () {
let stylusExample = '.outer {\n .inner { background: #fff; }\n }';
let componentContents = fs.readFileSync(componentFile, 'utf8');

sh.mv(cssFile, stylusFile);
sh.mv(cssFile, stylusFile);
fs.writeFileSync(stylusFile, stylusExample, 'utf8');
fs.writeFileSync(componentFile, componentContents.replace(new RegExp(cssFilename, 'g'), stylusFilename), 'utf8');

Expand Down

0 comments on commit a89cde0

Please sign in to comment.