diff --git a/README.md b/README.md
index 3d30a82d7e5a..7c2c639c0b39 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,7 @@ The generated project has dependencies that require **Node 4 or greater**.
* [Global styles](#global-styles)
* [CSS preprocessor integration](#css-preprocessor-integration)
* [3rd Party Library Installation](#3rd-party-library-installation)
+* [Global Library Installation](#global-library-installation)
* [Updating angular-cli](#updating-angular-cli)
* [Known Issues](#known-issues)
* [Development Hints for hacking on angular-cli](#development-hints-for-hacking-on-angular-cli)
@@ -250,6 +251,8 @@ The `styles.css` file allows users to add global styles and supports
If the project is created with the `--style=sass` option, this will be a `.sass`
file instead, and the same applies to `scss/less/styl`.
+You can add more global styles via the `apps[0].styles` property in `angular-cli.json`.
+
### CSS Preprocessor integration
Angular-CLI supports all major CSS preprocessors:
@@ -295,6 +298,42 @@ npm install moment --save
npm install @types/moment --save-dev
```
+### Global Library Installation
+
+Some javascript libraries need to be added to the global scope, and loaded as if
+they were in a script tag. We can do this using the `apps[0].scripts` and
+`apps[0].styles` properties of `angular-cli.json`.
+
+As an example, to use [Boostrap 4](http://v4-alpha.getbootstrap.com/) this is
+what you need to do:
+
+First install Bootstrap from `npm`:
+
+```bash
+npm install bootstrap@next
+```
+
+Then add the needed script files to to `apps[0].scripts`.
+
+```
+"scripts": [
+ "../node_modules/jquery/dist/jquery.js",
+ "../node_modules/tether/dist/js/tether.js",
+ "../node_modules/bootstrap/dist/js/bootstrap.js"
+],
+```
+
+Finally add the Bootstrap CSS to the `apps[0].styles` array:
+```
+"styles": [
+ "styles.css",
+ "../node_modules/bootstrap/dist/css/bootstrap.css"
+],
+```
+
+Restart `ng serve` if you're running it, and Bootstrap 4 should be working on
+your app.
+
### Updating angular-cli
To update `angular-cli` to a new version, you must update both the global package and your project's local package.
diff --git a/addon/ng2/blueprints/ng2/files/angular-cli.json b/addon/ng2/blueprints/ng2/files/angular-cli.json
index 33ccd58ef0f3..8e2ec1e19637 100644
--- a/addon/ng2/blueprints/ng2/files/angular-cli.json
+++ b/addon/ng2/blueprints/ng2/files/angular-cli.json
@@ -14,7 +14,10 @@
"tsconfig": "tsconfig.json",
"prefix": "<%= prefix %>",
"mobile": <%= isMobile %>,
- "styles": "styles.<%= styleExt %>",
+ "styles": [
+ "styles.<%= styleExt %>"
+ ],
+ "scripts": [],
"environments": {
"source": "environments/environment.ts",
"prod": "environments/environment.prod.ts",
diff --git a/addon/ng2/models/webpack-build-common.ts b/addon/ng2/models/webpack-build-common.ts
index 35cc21f82c79..591bf25dbf17 100644
--- a/addon/ng2/models/webpack-build-common.ts
+++ b/addon/ng2/models/webpack-build-common.ts
@@ -10,9 +10,18 @@ export function getWebpackCommonConfig(projectRoot: string, environment: string,
const appRoot = path.resolve(projectRoot, appConfig.root);
const appMain = path.resolve(appRoot, appConfig.main);
- const styles = path.resolve(appRoot, appConfig.styles);
+ const styles = appConfig.styles.map(style => path.resolve(appRoot, style));
+ const scripts = appConfig.scripts.map(script => path.resolve(appRoot, script));
const lazyModules = findLazyModules(appRoot);
+ let entry = {
+ main: [appMain]
+ };
+
+ // Only add styles/scripts if there's actually entries there
+ if (appConfig.styles.length > 0) entry.styles = styles;
+ if (appConfig.scripts.length > 0) entry.scripts = scripts;
+
return {
devtool: 'source-map',
resolve: {
@@ -20,9 +29,7 @@ export function getWebpackCommonConfig(projectRoot: string, environment: string,
root: appRoot
},
context: path.resolve(__dirname, './'),
- entry: {
- main: [appMain, styles]
- },
+ entry: entry,
output: {
path: path.resolve(projectRoot, appConfig.outDir),
filename: '[name].bundle.js'
@@ -66,6 +73,9 @@ export function getWebpackCommonConfig(projectRoot: string, environment: string,
{ include: styles, test: /\.less$/, loaders: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader'] },
{ include: styles, test: /\.scss$|\.sass$/, loaders: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'] },
+ // load global scripts using script-loader
+ { include: scripts, test: /\.js$/, loader: 'script-loader' },
+
{ test: /\.json$/, loader: 'json-loader' },
{ test: /\.(jpg|png)$/, loader: 'url-loader?limit=10000' },
{ test: /\.html$/, loader: 'raw-loader' },
@@ -90,6 +100,10 @@ export function getWebpackCommonConfig(projectRoot: string, environment: string,
.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&")),
path.resolve(appRoot, appConfig.environments[environment])
),
+ new webpack.optimize.CommonsChunkPlugin({
+ // Optimizing ensures loading order in index.html
+ name: ['styles', 'scripts', 'main'].reverse();
+ }),
new webpack.optimize.CommonsChunkPlugin({
minChunks: Infinity,
name: 'inline',
diff --git a/lib/config/schema.json b/lib/config/schema.json
index 168ad6a14e07..a971c033744a 100644
--- a/lib/config/schema.json
+++ b/lib/config/schema.json
@@ -49,27 +49,19 @@
"mobile": {
"type": "boolean"
},
- "additionalEntries": {
- "description": "Additional files to be included in the build.",
+ "styles": {
+ "description": "Global styles to be included in the build.",
"type": "array",
"items": {
- "anyOf": [
- {
- "type": "string"
- },
- {
- "type": "object",
- "properties": {
- "input": {
- "type": "string"
- },
- "output": {
- "type": "string"
- }
- },
- "additionalProperties": false
- }
- ]
+ "type": "string"
+ },
+ "additionalProperties": false
+ },
+ "scripts": {
+ "description": "Global scripts to be included in the build.",
+ "type": "array",
+ "items": {
+ "type": "string"
},
"additionalProperties": false
},
diff --git a/package.json b/package.json
index 13244ee47060..7ac6db5b15b9 100644
--- a/package.json
+++ b/package.json
@@ -78,6 +78,7 @@
"rimraf": "^2.5.3",
"rxjs": "^5.0.0-beta.11",
"sass-loader": "^3.2.0",
+ "script-loader": "^0.7.0",
"shelljs": "^0.7.0",
"silent-error": "^1.0.0",
"source-map-loader": "^0.1.5",
diff --git a/tests/e2e/e2e_workflow.spec.js b/tests/e2e/e2e_workflow.spec.js
index e72d63cb456e..a92ec8cc6501 100644
--- a/tests/e2e/e2e_workflow.spec.js
+++ b/tests/e2e/e2e_workflow.spec.js
@@ -486,7 +486,7 @@ describe('Basic end-to-end Workflow', function () {
expect(indexHtml).to.include('main.bundle.js');
});
- it('styles.css is added to main bundle', function() {
+ it('styles.css is added to styles bundle', function() {
this.timeout(420000);
let stylesPath = path.join(process.cwd(), 'src', 'styles.css');
@@ -495,10 +495,10 @@ describe('Basic end-to-end Workflow', function () {
sh.exec(`${ngBin} build`);
- var mainBundlePath = path.join(process.cwd(), 'dist', 'main.bundle.js');
- var mainBundleContent = fs.readFileSync(mainBundlePath, { encoding: 'utf8' });
+ var stylesBundlePath = path.join(process.cwd(), 'dist', 'styles.bundle.js');
+ var stylesBundleContent = fs.readFileSync(stylesBundlePath, { encoding: 'utf8' });
- expect(mainBundleContent.includes(testStyle)).to.be.equal(true);
+ expect(stylesBundleContent.includes(testStyle)).to.be.equal(true);
});
it('styles.css supports css imports', function() {
@@ -508,16 +508,62 @@ describe('Basic end-to-end Workflow', function () {
let testStyle = 'body { background-color: blue; }';
fs.writeFileSync(importedStylePath, testStyle, 'utf8');
- let stylesPath = path.join(process.cwd(), 'src', 'style.css');
- let importStyle = '@import \'./imported-style.css\';';
+ let stylesPath = path.join(process.cwd(), 'src', 'styles.css');
+ let importStyle = '@import \'./imported-styles.css\';';
fs.writeFileSync(stylesPath, importStyle, 'utf8');
sh.exec(`${ngBin} build`);
- var mainBundlePath = path.join(process.cwd(), 'dist', 'main.bundle.js');
- var mainBundleContent = fs.readFileSync(mainBundlePath, { encoding: 'utf8' });
+ var stylesBundlePath = path.join(process.cwd(), 'dist', 'styles.bundle.js');
+ var stylesBundleContent = fs.readFileSync(stylesBundlePath, { encoding: 'utf8' });
+
+ expect(stylesBundleContent).to.include(testStyle);
+ });
+
+ it('build supports global styles and scripts', function() {
+ this.timeout(420000);
+
+ sh.exec('npm install bootstrap@next', { silent: true });
+
+ const configFile = path.join(process.cwd(), 'angular-cli.json');
+ let originalConfigContent = fs.readFileSync(configFile, { encoding: 'utf8' });
+ let configContent = originalConfigContent.replace('"styles.css"', `
+ "styles.css",
+ "../node_modules/bootstrap/dist/css/bootstrap.css"
+ `).replace('"scripts": [],',`
+ "scripts": [
+ "../node_modules/jquery/dist/jquery.js",
+ "../node_modules/tether/dist/js/tether.js",
+ "../node_modules/bootstrap/dist/js/bootstrap.js"
+ ],
+ `);
+
+ fs.writeFileSync(configFile, configContent, 'utf8');
+
+ sh.exec(`${ngBin} build`);
- expect(mainBundleContent.includes(testStyle)).to.be.equal(true);
+ // checking for strings that are part of the included files
+ const stylesBundlePath = path.join(process.cwd(), 'dist', 'styles.bundle.js');
+ const stylesBundleContent = fs.readFileSync(stylesBundlePath, { encoding: 'utf8' });
+ expect(stylesBundleContent).to.include('* Bootstrap ');
+
+ const scriptsBundlePath = path.join(process.cwd(), 'dist', 'scripts.bundle.js');
+ const scriptsBundleContent = fs.readFileSync(scriptsBundlePath, { encoding: 'utf8' });
+ expect(scriptsBundleContent).to.include('* jQuery JavaScript');
+ expect(scriptsBundleContent).to.include('/*! tether ');
+ expect(scriptsBundleContent).to.include('* Bootstrap ');
+
+ // check the scripts are loaded in the correct order
+ const indexPath = path.join(process.cwd(), 'dist', 'index.html');
+ const indexContent = fs.readFileSync(indexPath, { encoding: 'utf8' });
+ let scriptTags = '' +
+ '' +
+ '' +
+ ''
+ expect(indexContent).to.include(scriptTags);
+
+ // restore config
+ fs.writeFileSync(configFile, originalConfigContent, 'utf8');
});
it('Serve and run e2e tests on dev environment', function () {