diff --git a/README.md b/README.md
index a32a257..2fced52 100644
--- a/README.md
+++ b/README.md
@@ -172,6 +172,43 @@ The previous configuration will expect and build the files:
- `src/index.html` => `dist/index.html`
- `src/index.js` => `dist/index.js`
+#### Excluding a page from chunks
+
+If you want a page to be excluded from either the [vendor](#chunksvendor) or [common](#chunkscommon) chunk, then you can do so by providing an object with a `name` and `independent` flag (set to `true`) instead of just the name of the page.
+
+```js
+// sagui.config.js
+module.exports = {
+ pages: ['index', 'about', { name: 'demo', independent: true }]
+}
+```
+
+### `chunks.vendor`
+
+If you want all your external dependencies (`node_modules`) in your [pages](#pages) to be bundled together in a "vendor" chunk, then set this flag to `true`. By default it is set to `false`.
+
+```js
+// sagui.config.js
+module.exports = {
+ chunks: {
+ vendor: true
+ }
+}
+```
+
+### `chunks.common`
+
+If you do not want all the common dependencies of your [pages](#pages) to be bundled together in a "common" chunk, then set this flag to `false`. By default it is set to `true`.
+
+```js
+// sagui.config.js
+module.exports = {
+ chunks: {
+ common: false
+ }
+}
+```
+
### `libraries`
Create **reusable libraries** that can be shared across applications. Sagui will take care of the build process so that external libraries are not bundled and that you have a CommonJS module as the output.
diff --git a/package.json b/package.json
index f6b3b2c..0efc2be 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "sagui",
- "version": "11.4.2",
+ "version": "11.5.0",
"description": "Front-end tooling in a single dependency",
"preferGlobal": false,
"bin": {
@@ -111,6 +111,7 @@
"karma-phantomjs-launcher": "^1.0.4",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^2.0.3",
+ "lodash.merge": "^4.6.0",
"lodash.uniq": "^4.5.0",
"node-sass": "^4.5.2",
"null-loader": "^0.1.1",
@@ -125,7 +126,7 @@
"url-loader": "^0.5.8",
"webpack": "^3.2.0",
"webpack-dev-server": "^2.5.1",
- "webpack-md5-hash": "0.0.5",
+ "webpack-md5-hash": "0.0.6",
"webpack-merge": "^4.1.0",
"yaml-loader": "^0.5.0"
}
diff --git a/spec/fixtures/project-content-with-invalid-import/src/index.spec.js b/spec/fixtures/project-content-with-invalid-import/src/index.spec.js
new file mode 100644
index 0000000..0c07d55
--- /dev/null
+++ b/spec/fixtures/project-content-with-invalid-import/src/index.spec.js
@@ -0,0 +1,5 @@
+import('name-that-is-invalid')
+
+describe('my project', function() {
+ it('should work not work at all', function() {})
+})
diff --git a/spec/fixtures/project-with-independent-page/node_modules/.DS_Store b/spec/fixtures/project-with-independent-page/node_modules/.DS_Store
new file mode 100644
index 0000000..5cd123b
Binary files /dev/null and b/spec/fixtures/project-with-independent-page/node_modules/.DS_Store differ
diff --git a/spec/fixtures/project-with-independent-page/node_modules/dependencyA/.DS_Store b/spec/fixtures/project-with-independent-page/node_modules/dependencyA/.DS_Store
new file mode 100644
index 0000000..5008ddf
Binary files /dev/null and b/spec/fixtures/project-with-independent-page/node_modules/dependencyA/.DS_Store differ
diff --git a/spec/fixtures/project-with-independent-page/node_modules/dependencyA/index.js b/spec/fixtures/project-with-independent-page/node_modules/dependencyA/index.js
new file mode 100644
index 0000000..56232d1
--- /dev/null
+++ b/spec/fixtures/project-with-independent-page/node_modules/dependencyA/index.js
@@ -0,0 +1,3 @@
+module.exports = function () {
+ return console.log('dependencyA')
+}
diff --git a/spec/fixtures/project-with-independent-page/node_modules/dependencyA/package.json b/spec/fixtures/project-with-independent-page/node_modules/dependencyA/package.json
new file mode 100644
index 0000000..c8ce753
--- /dev/null
+++ b/spec/fixtures/project-with-independent-page/node_modules/dependencyA/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "dependencyA",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "dependencies": {},
+ "devDependencies": {},
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC"
+}
diff --git a/spec/fixtures/project-with-independent-page/node_modules/dependencyB/index.js b/spec/fixtures/project-with-independent-page/node_modules/dependencyB/index.js
new file mode 100644
index 0000000..a449133
--- /dev/null
+++ b/spec/fixtures/project-with-independent-page/node_modules/dependencyB/index.js
@@ -0,0 +1,3 @@
+module.exports = function () {
+ return console.log('dependencyB')
+}
diff --git a/spec/fixtures/project-with-independent-page/node_modules/dependencyB/package.json b/spec/fixtures/project-with-independent-page/node_modules/dependencyB/package.json
new file mode 100644
index 0000000..f675a78
--- /dev/null
+++ b/spec/fixtures/project-with-independent-page/node_modules/dependencyB/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "dependencyB",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "dependencies": {},
+ "devDependencies": {},
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC"
+}
diff --git a/spec/fixtures/project-with-independent-page/sagui.config.js b/spec/fixtures/project-with-independent-page/sagui.config.js
new file mode 100644
index 0000000..206d080
--- /dev/null
+++ b/spec/fixtures/project-with-independent-page/sagui.config.js
@@ -0,0 +1,7 @@
+module.exports = {
+ pages: ['index', 'about', { name: 'demo', independent: true }],
+ chunks: {
+ vendor: true,
+ common: true,
+ }
+}
diff --git a/spec/fixtures/project-with-independent-page/src/about.html b/spec/fixtures/project-with-independent-page/src/about.html
new file mode 100644
index 0000000..08824ab
--- /dev/null
+++ b/spec/fixtures/project-with-independent-page/src/about.html
@@ -0,0 +1,11 @@
+
+
+
+ Hello
+
+
+
+
+
+
+
diff --git a/spec/fixtures/project-with-independent-page/src/about.js b/spec/fixtures/project-with-independent-page/src/about.js
new file mode 100644
index 0000000..cc26831
--- /dev/null
+++ b/spec/fixtures/project-with-independent-page/src/about.js
@@ -0,0 +1,5 @@
+import shared from './shared'
+import dependencyA from 'dependencyA'
+
+shared()
+dependencyA()
diff --git a/spec/fixtures/project-with-independent-page/src/demo.html b/spec/fixtures/project-with-independent-page/src/demo.html
new file mode 100644
index 0000000..08824ab
--- /dev/null
+++ b/spec/fixtures/project-with-independent-page/src/demo.html
@@ -0,0 +1,11 @@
+
+
+
+ Hello
+
+
+
+
+
+
+
diff --git a/spec/fixtures/project-with-independent-page/src/demo.js b/spec/fixtures/project-with-independent-page/src/demo.js
new file mode 100644
index 0000000..12daf59
--- /dev/null
+++ b/spec/fixtures/project-with-independent-page/src/demo.js
@@ -0,0 +1,7 @@
+import shared from './shared'
+import dependencyA from 'dependencyA'
+import dependencyB from 'dependencyB'
+
+shared()
+dependencyA()
+dependencyB()
diff --git a/spec/fixtures/project-with-independent-page/src/index.html b/spec/fixtures/project-with-independent-page/src/index.html
new file mode 100644
index 0000000..08824ab
--- /dev/null
+++ b/spec/fixtures/project-with-independent-page/src/index.html
@@ -0,0 +1,11 @@
+
+
+
+ Hello
+
+
+
+
+
+
+
diff --git a/spec/fixtures/project-with-independent-page/src/index.js b/spec/fixtures/project-with-independent-page/src/index.js
new file mode 100644
index 0000000..67de6da
--- /dev/null
+++ b/spec/fixtures/project-with-independent-page/src/index.js
@@ -0,0 +1,5 @@
+import shared from './shared'
+import dependencyB from 'dependencyB'
+
+shared()
+dependencyB()
diff --git a/spec/fixtures/project-with-independent-page/src/shared.js b/spec/fixtures/project-with-independent-page/src/shared.js
new file mode 100644
index 0000000..2ba7300
--- /dev/null
+++ b/spec/fixtures/project-with-independent-page/src/shared.js
@@ -0,0 +1 @@
+export default () => console.log('shared module')
diff --git a/spec/fixtures/project-with-two-pages-disabled-common/sagui.config.js b/spec/fixtures/project-with-two-pages-disabled-common/sagui.config.js
new file mode 100644
index 0000000..cf5fe22
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages-disabled-common/sagui.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ pages: ['index', 'about'],
+ chunks: {
+ common: false
+ }
+}
diff --git a/spec/fixtures/project-with-two-pages-disabled-common/src/about.html b/spec/fixtures/project-with-two-pages-disabled-common/src/about.html
new file mode 100644
index 0000000..08824ab
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages-disabled-common/src/about.html
@@ -0,0 +1,11 @@
+
+
+
+ Hello
+
+
+
+
+
+
+
diff --git a/spec/fixtures/project-with-two-pages-disabled-common/src/about.js b/spec/fixtures/project-with-two-pages-disabled-common/src/about.js
new file mode 100644
index 0000000..1781aab
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages-disabled-common/src/about.js
@@ -0,0 +1,3 @@
+import shared from './shared'
+
+shared()
diff --git a/spec/fixtures/project-with-two-pages-disabled-common/src/index.html b/spec/fixtures/project-with-two-pages-disabled-common/src/index.html
new file mode 100644
index 0000000..08824ab
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages-disabled-common/src/index.html
@@ -0,0 +1,11 @@
+
+
+
+ Hello
+
+
+
+
+
+
+
diff --git a/spec/fixtures/project-with-two-pages-disabled-common/src/index.js b/spec/fixtures/project-with-two-pages-disabled-common/src/index.js
new file mode 100644
index 0000000..1781aab
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages-disabled-common/src/index.js
@@ -0,0 +1,3 @@
+import shared from './shared'
+
+shared()
diff --git a/spec/fixtures/project-with-two-pages-disabled-common/src/shared.js b/spec/fixtures/project-with-two-pages-disabled-common/src/shared.js
new file mode 100644
index 0000000..2ba7300
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages-disabled-common/src/shared.js
@@ -0,0 +1 @@
+export default () => console.log('shared module')
diff --git a/spec/fixtures/project-with-two-pages-enabled-vendor/node_modules/.DS_Store b/spec/fixtures/project-with-two-pages-enabled-vendor/node_modules/.DS_Store
new file mode 100644
index 0000000..84549d0
Binary files /dev/null and b/spec/fixtures/project-with-two-pages-enabled-vendor/node_modules/.DS_Store differ
diff --git a/spec/fixtures/project-with-two-pages-enabled-vendor/node_modules/dependencyA/.DS_Store b/spec/fixtures/project-with-two-pages-enabled-vendor/node_modules/dependencyA/.DS_Store
new file mode 100644
index 0000000..5008ddf
Binary files /dev/null and b/spec/fixtures/project-with-two-pages-enabled-vendor/node_modules/dependencyA/.DS_Store differ
diff --git a/spec/fixtures/project-with-two-pages-enabled-vendor/node_modules/dependencyA/index.js b/spec/fixtures/project-with-two-pages-enabled-vendor/node_modules/dependencyA/index.js
new file mode 100644
index 0000000..56232d1
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages-enabled-vendor/node_modules/dependencyA/index.js
@@ -0,0 +1,3 @@
+module.exports = function () {
+ return console.log('dependencyA')
+}
diff --git a/spec/fixtures/project-with-two-pages-enabled-vendor/node_modules/dependencyA/package.json b/spec/fixtures/project-with-two-pages-enabled-vendor/node_modules/dependencyA/package.json
new file mode 100644
index 0000000..c8ce753
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages-enabled-vendor/node_modules/dependencyA/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "dependencyA",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "dependencies": {},
+ "devDependencies": {},
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC"
+}
diff --git a/spec/fixtures/project-with-two-pages-enabled-vendor/node_modules/dependencyB/index.js b/spec/fixtures/project-with-two-pages-enabled-vendor/node_modules/dependencyB/index.js
new file mode 100644
index 0000000..a449133
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages-enabled-vendor/node_modules/dependencyB/index.js
@@ -0,0 +1,3 @@
+module.exports = function () {
+ return console.log('dependencyB')
+}
diff --git a/spec/fixtures/project-with-two-pages-enabled-vendor/node_modules/dependencyB/package.json b/spec/fixtures/project-with-two-pages-enabled-vendor/node_modules/dependencyB/package.json
new file mode 100644
index 0000000..f675a78
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages-enabled-vendor/node_modules/dependencyB/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "dependencyB",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "dependencies": {},
+ "devDependencies": {},
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC"
+}
diff --git a/spec/fixtures/project-with-two-pages-enabled-vendor/sagui.config.js b/spec/fixtures/project-with-two-pages-enabled-vendor/sagui.config.js
new file mode 100644
index 0000000..911978b
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages-enabled-vendor/sagui.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ pages: ['index', 'about'],
+ chunks: {
+ vendor: true
+ }
+}
diff --git a/spec/fixtures/project-with-two-pages-enabled-vendor/src/about.html b/spec/fixtures/project-with-two-pages-enabled-vendor/src/about.html
new file mode 100644
index 0000000..08824ab
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages-enabled-vendor/src/about.html
@@ -0,0 +1,11 @@
+
+
+
+ Hello
+
+
+
+
+
+
+
diff --git a/spec/fixtures/project-with-two-pages-enabled-vendor/src/about.js b/spec/fixtures/project-with-two-pages-enabled-vendor/src/about.js
new file mode 100644
index 0000000..cc26831
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages-enabled-vendor/src/about.js
@@ -0,0 +1,5 @@
+import shared from './shared'
+import dependencyA from 'dependencyA'
+
+shared()
+dependencyA()
diff --git a/spec/fixtures/project-with-two-pages-enabled-vendor/src/index.html b/spec/fixtures/project-with-two-pages-enabled-vendor/src/index.html
new file mode 100644
index 0000000..08824ab
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages-enabled-vendor/src/index.html
@@ -0,0 +1,11 @@
+
+
+
+ Hello
+
+
+
+
+
+
+
diff --git a/spec/fixtures/project-with-two-pages-enabled-vendor/src/index.js b/spec/fixtures/project-with-two-pages-enabled-vendor/src/index.js
new file mode 100644
index 0000000..67de6da
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages-enabled-vendor/src/index.js
@@ -0,0 +1,5 @@
+import shared from './shared'
+import dependencyB from 'dependencyB'
+
+shared()
+dependencyB()
diff --git a/spec/fixtures/project-with-two-pages-enabled-vendor/src/shared.js b/spec/fixtures/project-with-two-pages-enabled-vendor/src/shared.js
new file mode 100644
index 0000000..2ba7300
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages-enabled-vendor/src/shared.js
@@ -0,0 +1 @@
+export default () => console.log('shared module')
diff --git a/spec/fixtures/project-with-two-pages/sagui.config.js b/spec/fixtures/project-with-two-pages/sagui.config.js
new file mode 100644
index 0000000..74ab9d0
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages/sagui.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ pages: ['index', 'about']
+}
diff --git a/spec/fixtures/project-with-two-pages/src/about.html b/spec/fixtures/project-with-two-pages/src/about.html
new file mode 100644
index 0000000..08824ab
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages/src/about.html
@@ -0,0 +1,11 @@
+
+
+
+ Hello
+
+
+
+
+
+
+
diff --git a/spec/fixtures/project-with-two-pages/src/about.js b/spec/fixtures/project-with-two-pages/src/about.js
new file mode 100644
index 0000000..1781aab
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages/src/about.js
@@ -0,0 +1,3 @@
+import shared from './shared'
+
+shared()
diff --git a/spec/fixtures/project-with-two-pages/src/index.html b/spec/fixtures/project-with-two-pages/src/index.html
new file mode 100644
index 0000000..08824ab
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages/src/index.html
@@ -0,0 +1,11 @@
+
+
+
+ Hello
+
+
+
+
+
+
+
diff --git a/spec/fixtures/project-with-two-pages/src/index.js b/spec/fixtures/project-with-two-pages/src/index.js
new file mode 100644
index 0000000..1781aab
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages/src/index.js
@@ -0,0 +1,3 @@
+import shared from './shared'
+
+shared()
diff --git a/spec/fixtures/project-with-two-pages/src/shared.js b/spec/fixtures/project-with-two-pages/src/shared.js
new file mode 100644
index 0000000..2ba7300
--- /dev/null
+++ b/spec/fixtures/project-with-two-pages/src/shared.js
@@ -0,0 +1 @@
+export default () => console.log('shared module')
diff --git a/spec/integration/index.integration-spec.js b/spec/integration/index.integration-spec.js
index b89b829..6012b32 100644
--- a/spec/integration/index.integration-spec.js
+++ b/spec/integration/index.integration-spec.js
@@ -41,6 +41,8 @@ describe('[integration] sagui', function () {
const projectContentCustomPrettierOptionsInEslintrc = path.join(__dirname, '../fixtures/project-content-with-custom-prettier-options-in-eslintrc')
const projectContentWithDynamicImports = path.join(__dirname, '../fixtures/project-content-with-dynamic-import')
const projectContentWithCaseMismatchInModulePath = path.join(__dirname, '../fixtures/project-with-case-mismatch-in-module-paths')
+ const projectContentWithInvalidImports = path.join(__dirname, '../fixtures/project-content-with-invalid-import')
+
let projectPath, projectSrcPath
beforeEach(function () {
@@ -137,6 +139,106 @@ describe('[integration] sagui', function () {
})
})
+ describe('chunks', () => {
+ describe('project with two pages on build', () => {
+ const projectFixture = path.join(__dirname, '../fixtures/project-with-two-pages')
+ beforeEach(function () {
+ fs.copySync(projectFixture, projectPath, { overwrite: true })
+ return sagui({ projectPath, action: actions.BUILD })
+ })
+
+ it('should have created a common.js file', () => {
+ const files = fs.readdirSync(path.join(projectPath, 'dist'))
+ expect(files.filter((file) => file.match(/common.+\.js$/))).not.to.be.empty
+ })
+
+ it('should NOT have created a vendor.js file', () => {
+ const files = fs.readdirSync(path.join(projectPath, 'dist'))
+ expect(files.filter((file) => file.match(/vendor.+\.js$/))).to.be.empty
+ })
+ })
+
+ describe('project with two pages and disabled common chunks on build', () => {
+ const projectFixture = path.join(__dirname, '../fixtures/project-with-two-pages-disabled-common')
+ beforeEach(function () {
+ fs.copySync(projectFixture, projectPath, { overwrite: true })
+ return sagui({ projectPath, action: actions.BUILD })
+ })
+
+ it('should not have created a common.js file', () => {
+ const files = fs.readdirSync(path.join(projectPath, 'dist'))
+ expect(files.filter((file) => file.match(/common.+\.js$/))).to.be.empty
+ })
+
+ it('should NOT have created a vendor.js file', () => {
+ const files = fs.readdirSync(path.join(projectPath, 'dist'))
+ expect(files.filter((file) => file.match(/vendor.+\.js$/))).to.be.empty
+ })
+ })
+
+ describe('project with two pages and enabled vendor chunks on build', () => {
+ const projectFixture = path.join(__dirname, '../fixtures/project-with-two-pages-enabled-vendor')
+ beforeEach(function () {
+ fs.copySync(projectFixture, projectPath, { overwrite: true })
+ return sagui({ projectPath, action: actions.BUILD })
+ })
+
+ it('should have created a common.js file without the content of the node_modules dependencies', () => {
+ const files = fs.readdirSync(path.join(projectPath, 'dist'))
+ const commonFiles = files.filter((file) => file.match(/common.+\.js$/))
+ const commonContent = fs.readFileSync(path.join(projectPath, 'dist', commonFiles[0])).toString()
+ expect(commonFiles).not.to.be.empty
+ expect(commonContent).not.to.have.string('dependencyA')
+ expect(commonContent).not.to.have.string('dependencyB')
+ })
+
+ it('should have created a vendor.js file with the content of the node_modules dependencies', () => {
+ const files = fs.readdirSync(path.join(projectPath, 'dist'))
+ const vendorFiles = files.filter((file) => file.match(/vendor.+\.js$/))
+ const vendorContent = fs.readFileSync(path.join(projectPath, 'dist', vendorFiles[0])).toString()
+ expect(vendorFiles).not.to.be.empty
+ expect(vendorContent).to.have.string('dependencyA')
+ expect(vendorContent).to.have.string('dependencyB')
+ })
+ })
+
+ describe('project with two pages but one that is independent with vendor and common chunks enabled', () => {
+ const projectFixture = path.join(__dirname, '../fixtures/project-with-independent-page')
+ beforeEach(function () {
+ fs.copySync(projectFixture, projectPath, { overwrite: true })
+ return sagui({ projectPath, action: actions.BUILD })
+ })
+
+ it('should have created a common.js file without the content of the node_modules dependencies, but with the shared content', () => {
+ const files = fs.readdirSync(path.join(projectPath, 'dist'))
+ const commonFiles = files.filter((file) => file.match(/common.+\.js$/))
+ const commonContent = fs.readFileSync(path.join(projectPath, 'dist', commonFiles[0])).toString()
+ expect(commonFiles).not.to.be.empty
+ expect(commonContent).not.to.have.string('dependencyA')
+ expect(commonContent).not.to.have.string('dependencyB')
+ expect(commonContent).to.have.string('shared')
+ })
+
+ it('should have created a vendor.js file with the content of the node_modules dependencies', () => {
+ const files = fs.readdirSync(path.join(projectPath, 'dist'))
+ const vendorFiles = files.filter((file) => file.match(/vendor.+\.js$/))
+ const vendorContent = fs.readFileSync(path.join(projectPath, 'dist', vendorFiles[0])).toString()
+ expect(vendorFiles).not.to.be.empty
+ expect(vendorContent).to.have.string('dependencyA')
+ expect(vendorContent).to.have.string('dependencyB')
+ })
+
+ it('should include all dependencies and shared code in the independent page', () => {
+ const files = fs.readdirSync(path.join(projectPath, 'dist'))
+ const aboutFiles = files.filter((file) => file.match(/demo.+\.js$/))
+ const aboutContent = fs.readFileSync(path.join(projectPath, 'dist', aboutFiles[0])).toString()
+ expect(aboutContent).to.have.string('dependencyA')
+ expect(aboutContent).to.have.string('dependencyB')
+ expect(aboutContent).to.have.string('shared')
+ })
+ })
+ })
+
describe('project with duplicated transient dependencies and colliding node_modules', () => {
const projectWithNodeModules = path.join(__dirname, '../fixtures/project-with-node-modules')
beforeEach(function () {
@@ -382,5 +484,16 @@ npm-debug.log`)
)
})
})
+
+ describe('when there are invalid imports', () => {
+ it('should not hang the test runner', async () => {
+ fs.copySync(projectContentWithInvalidImports, projectPath, { overwrite: true })
+ await sagui({ projectPath, action: actions.TEST_UNIT })
+ .then(
+ () => { throw new Error('It should have failed') },
+ (error) => expect(error).to.eql(1)
+ )
+ })
+ })
})
})
diff --git a/src/configure-webpack/build-pages-config.js b/src/configure-webpack/build-pages-config.js
index e936fed..87ace7f 100644
--- a/src/configure-webpack/build-pages-config.js
+++ b/src/configure-webpack/build-pages-config.js
@@ -3,11 +3,11 @@ import { join } from 'path'
import { optimize } from 'webpack'
import actions from '../actions'
-export default (pages = [], { action, projectPath }) => {
+export default (pages = [], { action, chunks = {}, projectPath }) => {
if (pages.length === 0 || action === actions.TEST_UNIT) { return {} }
const entry = configureEntry(pages)
- const plugins = configurePlugins(pages, action)
+ const plugins = configurePlugins(pages, chunks)
const filenamePattern = action === actions.BUILD
? '[name]-[chunkhash]'
: '[name]' // For better performance during development
@@ -27,24 +27,63 @@ function configureEntry (pages) {
let entry = {}
pages.forEach((page) => {
- entry[page] = [`./${page}`]
+ const pageName = getPageName(page)
+ entry[pageName] = [`./${pageName}`]
})
return entry
}
-function configurePlugins (pages, action) {
+function configurePlugins (pages, chunksConfig) {
const plugins = pages.map((page) => {
+ const pageName = getPageName(page)
+ const chunks = Object.keys(chunksConfig)
+ .reduce((chunks, key) => {
+ if (chunksConfig[key]) {
+ chunks.unshift(key)
+ }
+ return chunks
+ }, [pageName])
+
return new HtmlWebpackPlugin({
- template: `${page}.html`,
- filename: `${page}.html`,
- chunks: ['common', page]
+ template: `${pageName}.html`,
+ filename: `${pageName}.html`,
+ chunks
})
})
if (pages.length > 1) {
- plugins.push(new optimize.CommonsChunkPlugin({ name: 'common', minChunks: 2 }))
+ const chunks = pages.reduce((chunks, page) => {
+ if (typeof page === 'string') {
+ chunks.push(page)
+ }
+ if (page.independent !== true) {
+ chunks.push(page.name)
+ }
+ return chunks
+ }, [])
+
+ if (chunksConfig.vendor) {
+ plugins.push(new optimize.CommonsChunkPlugin({
+ name: 'vendor',
+ minChunks: (module) => {
+ return /node_modules/.test(module.context)
+ },
+ chunks
+ }))
+ }
+ if (chunksConfig.common) {
+ plugins.push(new optimize.CommonsChunkPlugin({
+ name: 'common',
+ minChunks: 2,
+ chunks
+ }))
+ }
}
return plugins
}
+
+function getPageName (page) {
+ return (typeof page === 'string' ? page : page.name)
+}
diff --git a/src/configure-webpack/build-pages-config.spec.js b/src/configure-webpack/build-pages-config.spec.js
index 4a33e77..44a3d57 100644
--- a/src/configure-webpack/build-pages-config.spec.js
+++ b/src/configure-webpack/build-pages-config.spec.js
@@ -41,7 +41,7 @@ describe('pages webpack preset', function () {
})
it('should have a plugin setting up the HTML template', function () {
- const webpackConfig = buildPagesConfig(['index'], baseConfig)
+ const webpackConfig = buildPagesConfig(['index'], { chunks: { common: true }, ...baseConfig })
const html = webpackConfig.plugins.filter((plugin) => plugin instanceof HtmlWebpackPlugin)
expect(html.length).equal(1)
@@ -73,35 +73,127 @@ describe('pages webpack preset', function () {
})
})
- it('should have a plugin setting up the HTML template for each chunk', function () {
- const webpackConfig = buildPagesConfig(['index', 'demo'], baseConfig)
+ describe('when chunks.common is "true"', function () {
+ it('should have a plugin setting up the HTML template for each chunk', function () {
+ const webpackConfig = buildPagesConfig(['index', 'demo'], { chunks: { common: true }, ...baseConfig })
- const html = webpackConfig.plugins.filter((plugin) => plugin instanceof HtmlWebpackPlugin)
- expect(html.length).equal(2)
+ const html = webpackConfig.plugins.filter((plugin) => plugin instanceof HtmlWebpackPlugin)
+ expect(html.length).equal(2)
- const firstOptions = html[0].options
- expect(firstOptions.chunks).deep.eql([ 'common', 'index' ])
- expect(firstOptions.filename).deep.eql('index.html')
- expect(firstOptions.template).deep.eql('index.html')
+ const firstOptions = html[0].options
+ expect(firstOptions.chunks).deep.eql([ 'common', 'index' ])
+ expect(firstOptions.filename).deep.eql('index.html')
+ expect(firstOptions.template).deep.eql('index.html')
- const secondOptions = html[1].options
- expect(secondOptions.chunks).deep.eql([ 'common', 'demo' ])
- expect(secondOptions.filename).deep.eql('demo.html')
- expect(secondOptions.template).deep.eql('demo.html')
- })
+ const secondOptions = html[1].options
+ expect(secondOptions.chunks).deep.eql([ 'common', 'demo' ])
+ expect(secondOptions.filename).deep.eql('demo.html')
+ expect(secondOptions.template).deep.eql('demo.html')
+ })
- it('should have the CommonsChunkPlugin enabled', function () {
- const webpackConfig = buildPagesConfig(['index', 'demo'], baseConfig)
+ it('should have the CommonsChunkPlugin enabled', function () {
+ const webpackConfig = buildPagesConfig(['index', 'demo'], { chunks: { common: true }, ...baseConfig })
- const commons = webpackConfig.plugins.filter((plugin) => plugin instanceof optimize.CommonsChunkPlugin)
- expect(commons.length).equal(1)
+ const commons = webpackConfig.plugins.filter((plugin) => plugin instanceof optimize.CommonsChunkPlugin)
+ expect(commons.length).equal(1)
+ })
+
+ it('should set name to "common" in the CommonsChunkPlugin', function () {
+ const webpackConfig = buildPagesConfig(['index', 'demo'], { chunks: { common: true }, ...baseConfig })
+
+ const commons = webpackConfig.plugins.filter((plugin) => plugin instanceof optimize.CommonsChunkPlugin)
+ expect(commons[0].chunkNames[0]).equal('common')
+ })
+
+ it('should set minChunks to 2 in the CommonsChunkPlugin', function () {
+ const webpackConfig = buildPagesConfig(['index', 'demo'], { chunks: { common: true }, ...baseConfig })
+
+ const commons = webpackConfig.plugins.filter((plugin) => plugin instanceof optimize.CommonsChunkPlugin)
+ expect(commons[0].minChunks).equal(2)
+ })
+
+ it('should set pages as chunks in the CommonsChunkPlugin', function () {
+ const webpackConfig = buildPagesConfig(['index', 'demo'], { chunks: { common: true }, ...baseConfig })
+
+ const commons = webpackConfig.plugins.filter((plugin) => plugin instanceof optimize.CommonsChunkPlugin)
+ expect(commons[0].selectedChunks[0]).equal('index')
+ expect(commons[0].selectedChunks[2]).equal('demo')
+ })
})
- it('should set minChunks to 2 in the CommonsChunkPlugin', function () {
- const webpackConfig = buildPagesConfig(['index', 'demo'], baseConfig)
+ describe('when chunks.vendor is "true"', function () {
+ it('should have a plugin setting up the HTML template for each chunk', function () {
+ const webpackConfig = buildPagesConfig(['index', 'demo'], { chunks: { vendor: true }, ...baseConfig })
- const commons = webpackConfig.plugins.filter((plugin) => plugin instanceof optimize.CommonsChunkPlugin)
- expect(commons[0].minChunks).equal(2)
+ const html = webpackConfig.plugins.filter((plugin) => plugin instanceof HtmlWebpackPlugin)
+ expect(html.length).equal(2)
+
+ const firstOptions = html[0].options
+ expect(firstOptions.chunks).deep.eql([ 'vendor', 'index' ])
+ expect(firstOptions.filename).deep.eql('index.html')
+ expect(firstOptions.template).deep.eql('index.html')
+
+ const secondOptions = html[1].options
+ expect(secondOptions.chunks).deep.eql([ 'vendor', 'demo' ])
+ expect(secondOptions.filename).deep.eql('demo.html')
+ expect(secondOptions.template).deep.eql('demo.html')
+ })
+
+ it('should have the CommonsChunkPlugin enabled', function () {
+ const webpackConfig = buildPagesConfig(['index', 'demo'], { chunks: { vendor: true }, ...baseConfig })
+
+ const vendor = webpackConfig.plugins.filter((plugin) => plugin instanceof optimize.CommonsChunkPlugin)
+ expect(vendor.length).equal(1)
+ })
+
+ it('should set name to "vendor" in the CommonsChunkPlugin', function () {
+ const webpackConfig = buildPagesConfig(['index', 'demo'], { chunks: { vendor: true }, ...baseConfig })
+
+ const vendor = webpackConfig.plugins.filter((plugin) => plugin instanceof optimize.CommonsChunkPlugin)
+ expect(vendor[0].chunkNames[0]).equal('vendor')
+ })
+
+ it('should set minChunks to a function in the CommonsChunkPlugin', function () {
+ const webpackConfig = buildPagesConfig(['index', 'demo'], { chunks: { vendor: true }, ...baseConfig })
+
+ const vendor = webpackConfig.plugins.filter((plugin) => plugin instanceof optimize.CommonsChunkPlugin)
+ expect(vendor[0].minChunks).to.be.a('function')
+ })
+
+ it('should set pages as chunks in the CommonsChunkPlugin', function () {
+ const webpackConfig = buildPagesConfig(['index', 'demo'], { chunks: { vendor: true }, ...baseConfig })
+
+ const vendor = webpackConfig.plugins.filter((plugin) => plugin instanceof optimize.CommonsChunkPlugin)
+ expect(vendor[0].selectedChunks[0]).equal('index')
+ expect(vendor[0].selectedChunks[2]).equal('demo')
+ })
+ })
+
+ describe('when a page is flagged as independent', function () {
+ it('should be excluded from both the vendor and common chunks', function () {
+ const webpackConfig = buildPagesConfig([
+ 'index',
+ { name: 'demo', independent: true }
+ ], {
+ chunks: {
+ vendor: true,
+ common: true
+ },
+ ...baseConfig
+ })
+
+ const plugins = webpackConfig.plugins.filter((plugin) => plugin instanceof optimize.CommonsChunkPlugin)
+ const vendor = plugins[0]
+ const common = plugins[1]
+
+ expect(vendor.selectedChunks.length).equal(2)
+ expect(vendor.selectedChunks[0]).equal('index')
+ expect(vendor.selectedChunks[2]).equal(undefined)
+
+ expect(common.selectedChunks.length).equal(2)
+ expect(common.selectedChunks[0]).equal('index')
+ expect(common.selectedChunks[2]).equal(undefined)
+ })
})
})
diff --git a/src/index.js b/src/index.js
index c45c2da..ed99ced 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,4 +1,5 @@
import path from 'path'
+import merge from 'lodash.merge'
import cli from './cli'
import loadProjectSaguiConfig from './load-project-sagui-config'
import configureKarma from './configure-karma'
@@ -19,6 +20,10 @@ const DEFAULT_SAGUI_CONFIG = {
optimize: false,
coverage: false,
pages: [],
+ chunks: {
+ vendor: false,
+ common: true
+ },
disableLoaders: [],
javaScript: {},
additionalWebpackConfig: {},
@@ -34,12 +39,7 @@ const DEFAULT_SAGUI_CONFIG = {
*/
const sagui = (saguiConfig = {}) => new Promise((resolve, reject) => {
try {
- const finalSaguiConfig = {
- ...DEFAULT_SAGUI_CONFIG,
- ...saguiConfig,
- ...loadProjectSaguiConfig(saguiConfig)
- }
-
+ const finalSaguiConfig = merge({}, DEFAULT_SAGUI_CONFIG, saguiConfig, loadProjectSaguiConfig(saguiConfig))
const webpackConfig = configureWebpack(finalSaguiConfig)
const karmaConfig = configureKarma(finalSaguiConfig, webpackConfig)
diff --git a/src/sagui-config-schema.json b/src/sagui-config-schema.json
index 1744f73..dd78aaa 100644
--- a/src/sagui-config-schema.json
+++ b/src/sagui-config-schema.json
@@ -67,8 +67,38 @@
"description": "https://github.com/saguijs/sagui#libraries"
},
"pages": {
- "description": "https://github.com/saguijs/sagui#pages",
- "type": "array"
+ "anyOf": [
+ {
+ "type": "array"
+ },
+ {
+ "additionalProperties": false,
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "independent": {
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ ],
+ "description": "https://github.com/saguijs/sagui#pages"
+ },
+ "chunks": {
+ "additionalProperties": false,
+ "properties": {
+ "common": {
+ "description": "https://github.com/saguijs/sagui#chunkscommon",
+ "type": "boolean"
+ },
+ "vendor": {
+ "description": "https://github.com/saguijs/sagui#chunksvendor",
+ "type": "boolean"
+ }
+ },
+ "type": "object"
},
"style": {
"additionalProperties": false,