Skip to content

Commit

Permalink
[WIP] Webpack 5 Support (#2303)
Browse files Browse the repository at this point in the history
* Update dependencies

* Update node version

12.x is the current LTS release

* Update plugins

Tappable methods were deprecated in webpack 4 and webpack 5 now requires the use of hooks.

* Rework build callback plugin test

Use a real compiler because the compiler internals can change, and in this case, have changed.

* Work around Vue Loader webpack 5 bug

* Remove purifycss support

PurifyCSS webpack is no longer maintained and it’s recommended to use PurgeCSS with PostCSS instead

* Fix webpack rule schema

* Centralize split chunk creation

* Work on CSS extraction

extract-text-webpack-plugin was not really compatible witht webpack 4 but kinda worked in many cases. It will not work with webpack 5 at all.

* Move CSS to proper file

And there’s no extra empty(-ish) JS files! 🎉

* Handle Vue style extraction

* Remove extra chunks

* Update extraction handling

* Temporarily remove file

* Update deps

Tests pass. Updating ava breaks things. :/

* Upgrade webpack

* Refactor CSS chunk generation

* Don’t eagerly extract all CSS

Before this was extracting all CSS from every call into the same chunk. This is not what we want espcially when there are multiple preprocessor calls.

* Mostly fix Vue CSS extraction

* Properly extract PostCSS styles

* Update tests

* Update testts

Have to fix the extra generated files

* Fix unnecessary style extraction

* Fix tests

* Reset chunks for each test

* Tweak chunk generation

* Ensure Vue styles are always appended when requested

* Fix test

* Cleanup code

* Fix less preprocessing

* Run tests on Node 12+

* Fix npm installation on AppVeyor

npm ci only works with a lockfile

* Fix production builds

1. This class no longer exists and is controlled by an optimization key.
2. This is the default in production builds for webpack

* Add test for previous production build fix

* Fix total vendor extraction

* Disable default vendor extraction

Async split chunks that share modules will extract shared modules. This is a good thing but it likely doesn’t match user expectations and won’t always get placed into the appropriate folder.

* Remove unused method

* Make extract tests check build results

* Fix extraction

* Update to latest webpack 5 beta

* Return promise from compile

* Add integration test for js compilation

* Add failing integration test for js + css compilation

When this test passes we’ll know the problem has been fixed

* Refactor

* Add plugin to remove CSS only chunks

This seems to work. Hopefully it’s not terribly broken…

* Refactor

* Normalize output paths when starting with the public folder name

* Update browsersync plugin

* Remove console log

* Fix broken test

* Fix pre-proceessor chunk splitting

* Restore support for global vue styles

This also allows global vue styles to work with any preprocessor Mix supports

* Rename for consistency

* Update tests

* Fix public path handling on windows
  • Loading branch information
thecrypticace committed May 12, 2020
1 parent 8f1a87e commit a3a680b
Show file tree
Hide file tree
Showing 53 changed files with 1,168 additions and 587 deletions.
8 changes: 4 additions & 4 deletions .appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Test against all supported versions of Node.js on windows
environment:
matrix:
- nodejs_version: '8'
- nodejs_version: '9'
- nodejs_version: '10'
- nodejs_version: '12'
- nodejs_version: '13'
- nodejs_version: '14'

cache:
- node_modules
Expand All @@ -14,7 +14,7 @@ install:
# Get the version of Node.js
- ps: Install-Product node $env:nodejs_version
- npm install --global npm@latest
- npm ci
- npm install

# Post-install test scripts.
test_script:
Expand Down
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ os:

# Test against all supported versions of Node.js
node_js:
- '8'
- '10'
- '12'
- '13'
- '14'

# Run ESLint after npm test
script:
Expand Down
55 changes: 29 additions & 26 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "laravel-mix",
"version": "5.0.4",
"version": "6.0.0-alpha.0",
"description": "Laravel Mix is an elegant wrapper around Webpack for the 80% use case.",
"main": "src/index.js",
"scripts": {
Expand Down Expand Up @@ -39,36 +39,38 @@
"@babel/runtime": "^7.2.0",
"autoprefixer": "^9.4.2",
"babel-loader": "^8.0.4",
"babel-merge": "^2.0.1",
"chokidar": "^2.0.3",
"babel-merge": "^3.0.0",
"chokidar": "^3.3.1",
"clean-css": "^4.1.3",
"collect.js": "^4.12.8",
"concat": "^1.0.3",
"css-loader": "^1.0.1",
"dotenv": "^6.2.0",
"dotenv-expand": "^4.2.0",
"css-loader": "^3.4.2",
"dotenv": "^8.2.0",
"dotenv-expand": "^5.1.0",
"extract-text-webpack-plugin": "v4.0.0-beta.0",
"file-loader": "^2.0.0",
"friendly-errors-webpack-plugin": "^1.6.1",
"fs-extra": "^7.0.1",
"file-loader": "^6.0.0",
"friendly-errors-webpack-plugin": "2.0.0-beta.2",
"fs-extra": "^9.0.0",
"glob": "^7.1.2",
"html-loader": "^0.5.5",
"imagemin": "^6.0.0",
"html-loader": "^1.0.0",
"imagemin": "^7.0.1",
"img-loader": "^3.0.0",
"lodash": "^4.17.15",
"md5": "^2.2.1",
"mini-css-extract-plugin": "^0.9.0",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"postcss-loader": "^3.0.0",
"style-loader": "^0.23.1",
"terser": "^3.11.0",
"terser-webpack-plugin": "^2.2.3",
"vue-loader": "^15.4.2",
"webpack": "^4.36.1",
"webpack-cli": "^3.1.2",
"style-loader": "^1.1.3",
"temp-sandbox": "^4.0.1",
"terser": "^4.6.7",
"terser-webpack-plugin": "^2.3.1",
"vue-loader": "^15.9.1",
"webpack": "5.0.0-beta.16",
"webpack-cli": "4.0.0-beta.1",
"webpack-dev-server": "^3.1.14",
"webpack-merge": "^4.1.0",
"webpack-merge": "^4.2.2",
"webpack-notifier": "^1.5.1",
"yargs": "^12.0.5"
"yargs": "^15.3.1"
},
"devDependencies": {
"@babel/preset-react": "^7.0.0",
Expand All @@ -77,28 +79,29 @@
"coffeescript": "^2.2.3",
"eol": "^0.9.1",
"eslint": "^6.0.1",
"husky": "^1.2.0",
"husky": "^4.2.3",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"less-loader": "^5.0.0",
"mock-require": "^3.0.2",
"nyc": "^14.1.1",
"postcss-custom-properties": "^8.0.9",
"nyc": "^15.0.0",
"playwright": "^1.0.1",
"postcss-custom-properties": "^9.1.1",
"prettier": "1.15.2",
"pretty-quick": "^1.8.0",
"pretty-quick": "^2.0.1",
"purify-css": "^1.2.6",
"purifycss-webpack": "^0.7.0",
"resolve-url-loader": "^3.1.0",
"sass": "^1.15.1",
"sass-loader": "^8.0.0",
"sass-resources-loader": "^2.0.0",
"sinon": "^7.1.1",
"sinon": "^9.0.1",
"stylus": "^0.54.5",
"stylus-loader": "^3.0.2",
"vue": "^2.5.21",
"vue-style-loader": "^4.0.2",
"vue-template-compiler": "^2.5.21"
},
"engines": {
"node": ">=8.9.0"
"node": ">=12.14.0"
}
}
207 changes: 207 additions & 0 deletions src/Chunks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
let instance;

/** @typedef {import("webpack/declarations/WebpackOptions").OptimizationSplitChunksCacheGroup} CacheGroup */

/**
* @typedef {(module: import("webpack").Module, chunks: import("webpack").ChunkData[]) => bool} ChunkTestCallback
* @typedef {undefined|boolean|string|RegExp|ChunkTestCallback} ChunkTest
*/

/**
* @typedef {(chunk: CacheGroup, id: string) => bool} ChunkFindCallback
*/

class Chunks {
constructor() {
/** @type {{[key: string]: CacheGroup}} */
this.chunks = {};

this.entry = null;
this.runtime = false;
}

/**
* @return {Chunks}
*/
static instance() {
return instance || this.reset();
}

/**
* @return {Chunks}
*/
static reset() {
return (instance = new Chunks());
}

/**
*
* @param {string} id A unique identifier for this chunk. Multiple chunks with the same ID are merged.
* @param {string} path The output path for this chunk
* @param {ChunkTest|ChunkTest[]} test A test that tells webpack how to determine what to put in this chunk
* @param {Partial<CacheGroup>} attrs
*/
add(id, path, test, attrs = {}) {
this.create(id, path, attrs).addTo(id, test);
}

/**
*
* @param {string} id A unique identifier for this chunk. Multiple chunks with the same ID are merged.
* @param {string} path The output path for this chunk
* @param {Partial<CacheGroups>} attrs
*/
create(id, path, attrs = {}) {
this.chunks[id] = {
name: path,
...attrs
};

return this;
}

/**
*
* @param {string} idOrPath
* @param {ChunkTest|ChunkTest[]} test
*/
addTo(idOrPath, test) {
const chunk = this.find(idOrPath);

if (Array.isArray(test)) {
test = this._checkAllTests(test);
}

if (chunk.test) {
test = this._checkAnyTests([chunk.test, test]);
}

chunk.test = test;

return this;
}

/**
*
* @param {string|ChunkFindCallback} idOrPath
* @returns {CacheGroup|null}
*/
find(idOrPath) {
if (typeof idOrPath === 'string') {
if (this.chunks[idOrPath]) {
return this.chunks[idOrPath];
}

return this.find((_, id) => id === idOrPath);
}

const item = Object.entries(this.chunks).find(([id, chunk]) =>
idOrPath(chunk, id)
);

return item ? item[1] : null;
}

config() {
return {
optimization: {
...this.runtimeChunk(),
...this.splitChunks()
}
};
}

runtimeChunk() {
if (!this.runtime || !this.entry) {
return {};
}

return {
runtimeChunk: {
name: path.join(this.entry.base, 'manifest').replace(/\\/g, '/')
}
};
}

splitChunks() {
return {
splitChunks: {
...this.cacheGroups()
}
};
}

cacheGroups() {
return {
cacheGroups: {
default: false,
defaultVendors: false,
...this.chunks
}
};
}

/**
* Check to see if a chunk should be included based on multiple tests
*
* This is for internal use only and may be changed or removed at any time
*
* @internal
*
* @param {(undefined|boolean|string|RegExp|Function)[]} tests
* @param {Module} module the module
* @param {CacheGroupsContext} context context object
*/
_checkAllTests(tests) {
return (module, context) =>
tests.every(test => this._checkTest(test, module, context));
}

/**
* Check to see if a chunk should be included based on multiple tests
*
* This is for internal use only and may be changed or removed at any time
*
* @internal
*
* @param {(undefined|boolean|string|RegExp|Function)[]} tests
* @param {Module} module the module
* @param {CacheGroupsContext} context context object
*/
_checkAnyTests(tests) {
return (module, context) =>
tests.some(test => this._checkTest(test, module, context));
}

/**
* Check to see if a chunk should be included
*
* NOTE: This repeats the code from the SplitChunksPlugin checkTest function
* This is for internal use only and may be changed or removed at any time
*
* @internal
*
* @param {undefined|boolean|string|RegExp|Function} test test option
* @param {Module} module the module
* @param {CacheGroupsContext} context context object
* @returns {boolean} true, if the module should be selected
*/
_checkTest(test, module, context) {
if (test === undefined) return true;
if (typeof test === 'function') {
return test(module, context);
}
if (typeof test === 'boolean') return test;
if (typeof test === 'string') {
const name = module.nameForCondition();
return name && name.startsWith(test);
}
if (test instanceof RegExp) {
const name = module.nameForCondition();
return name && test.test(name);
}
return false;
}
}

module.exports.Chunks = Chunks;

0 comments on commit a3a680b

Please sign in to comment.