This is the 9th day of my participation in the August More Text Challenge. For details, see:August is more challenging
Currently all of our WebPack configuration information is in one configuration file :webpack.config.js
As more and more configurations are made, this file becomes less and less maintainable;
And some configurations are needed in the development environment, some configurations are needed in the build environment, and of course some configurations are needed in both the development and build environments
Therefore, we’d better divide the configuration to facilitate our maintenance and management;
Differentiate the development environment
Environment: In the project root directory, there is a folder called config, which is used to store webpack configuration files
Method 1: Write two different configuration files, and load each configuration file during development and generation
package.json
"scripts": {
"dev": "webpack --config ./config/config.dev.js",
"prod": "webpack --config ./config/config.prod.js"
}
Copy the code
Option 2: Use the same entry profile and set parameters to distinguish them
package.json
"scripts": {
"dev": "webpack --config ./config/config.common.js --env development"."prod": "webpack --config ./config/config.common.js --env production"
}
Copy the code
config.common.js
const path = require('path')
module.exports = env= > {
// development ---> { WEBPACK_BUNDLE: true, WEBPACK_BUILD: true, development: true }
// production ---> { WEBPACK_BUNDLE: true, WEBPACK_BUILD: true, production: true }
console.log(env)
return {
// You must write./ You cannot write.. / Otherwise the entry file cannot be found correctly
entry: './src/index.js'.output: {
// Since the current directory is under config, the build directory needs to be stored in the directory above
path: path.resolve(__dirname, '.. /dist'),
filename: 'bundle.js'}}}Copy the code
context
Our previous rule for writing entry files was :./ SRC /index.js, but if our configuration files are located in the config directory, should we change to.. / SRC/index. Js? Obviously not
That’s because the entry file is actually a context that’s related to another property
The context is used to parse entry points and loaders.
const path = require('path')
module.exports = env= > {
return {
// The value of context is required to be an absolute path
// The actual entry file path is path.resolve(context, entry).
context: path.resolve(__dirname, '/'),
// Because the context is set to the current path, which is the config directory
entry: '.. /src/index.js'.output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'}}}Copy the code
The default context value is the path where the command line is executed
#So the default path of the context is the root of the project
webpack --config ./config/config.common.js --env development
Copy the code
Because, by default, the entry path should be set to ‘./ SRC /index.js’ instead of ‘.. /src/index.js’
Configuration file separation
When we set mode, Webpack will automatically set the mode value to process.env.node_env
For example, mode: ‘development ===> process.env.node_env is development
path.js
const path = require('path')
// The process.cwd() method in node outputs the directory where the current node process is executed
// The node process is started in the project root directory (started in pacakge.json)
// So the result of process.cwd() is the project root directory
module.exports = (relativePath) = > path.resolve(process.cwd(), relativePath)
Copy the code
config.common.js
const path = require('path')
// The webpack-merge library helps you merge configuration files with each other
const { merge } = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const prodConfig = require('./config.prod')
const devConfig = require('./config.dev')
const resolvePath = require('.. /utils/path')
module.exports = env= > {
Process.env.node_env is used to set the global environment value in advance
// So that Babel can obtain environment variables during initial configuration
// tips: If one of the attributes of process.env.node_env is set to undefined
// When we retrieve the value of the process.env.node_env attribute, the result is undefined, but the type is string, not undefined
process.env.NODE_ENV = env.production ? 'production' : 'development'
const commonConfig = {
entry: './src/index.js'.output: {
path: path.resolve(__dirname, '.. /dist'),
filename: 'bundle.js'
},
devServer: {
hot: true
},
plugins: [
new HtmlWebpackPlugin({
template: resolvePath('./public/index.html')]}})return env === 'production' ? merge(commonConfig, prodConfig) : merge(commonConfig, devConfig)
}
Copy the code
config.dev.js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
module.exports = {
mode: 'development'.plugins: [
new ReactRefreshWebpackPlugin()
]
}
Copy the code
config.prod.js
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
mode: 'production'.plugins: [
new CleanWebpackPlugin()
]
}
Copy the code
babel.config.js
const presets = [
'@babel/preset-env'
]
const plugins = []
// Process.env.node_env gets its value because the environment variable is set in common.config.js
// Not because webpack helps us set the mode value
// When Babel is initialized, webpack is not ready to set the environment variables
// So we need to display the global environment variables first
if (process.env.NODE_ENV === 'development') {
plugins.push('react-refresh/babel')}module.exports = {
presets,
plugins
}
Copy the code
package.json
"scripts": {
"dev": "webpack serve --config ./config/config.common.js --env development"."prod": "webpack --config ./config/config.common.js --env production"
}
Copy the code
The separation
Code Splitting is a very important feature of Webpack:
By default, all JavaScript code (business code, third-party dependencies, temporarily unused modules) is loaded on the home page, which affects the loading speed of the home page.
The main goal is to separate the code into different bundles, which we can then load on demand or load in parallel
Code separation allows for smaller bundles and control over resource load priorities to provide code load performance
There are three common types of code separation in Webpack:
-
Entry starting point: Manually separate the code using the Entry configuration
-
Remove duplicate modules: Use Entry Dependencies or SplitChunksPlugin to duplicate and separate code
-
Dynamic import: code is separated by inline function calls to modules;
Entrance to the starting point
The meaning of the entry starting point is very simple, which is to configure multiple entries
module.exports = {
entry: {
main: './src/main.js'.index: './src/index.js'
},
output: {
path: path.resolve(__dirname, '.. /dist'),
// In this case, the name value is the key value of the entry
// The package will form main.bundle.js and index.bundle.js
filename: '[name].bundle.js'}}Copy the code
Remove duplicate modules
Entry Dependencies
Suppose our index.js and main.js both rely on two libraries :lodash and dayjs
If we simply separate the entry, then both bunlde packs will have a copy of Lodash and dayJS
We can actually share them
entry: {
main: {
import: './src/main.js'.// Main.js needs to extract the dependency, which is a string value
dependOn: 'lodash'
},
index: {
import: './src/index.js'.dependOn: 'lodash'
},
// Pull out the public module
lodash: 'lodash'.dayjs: 'dayjs'
}
Copy the code
If there are multiple modules that need to be shared, you can separate the modules into an array and use them together
entry: {
main: {
import: './src/main.js'.dependOn: 'shared'
},
index: {
import: './src/index.js'.dependOn: 'shared'
},
// An array of modules that need to be shared
shared: ["lodash"."dayjs"]}Copy the code
SplitChunks
Another subcontracting mode is splitChunk, which is implemented using SplitChunksPlugin:
Since the webPack plug-in is installed and integrated by default, we do not need to install and use the plug-in directly. We only need to provide configuration information related to SplitChunksPlugin
optimization: {
splitChunks: {
// Chunks can be set to three values
// 1. Async Subcontracts packages introduced by async by default
// 2. Inital subcontracts the synchronously imported packages
// 3. All Subcontracts all imported packages
chunks: 'all'}}Copy the code
SplitChunks can also have additional funding options
optimization: {
splitChunks: {
chunks: 'all'.// The default value is 20000bytes, which means that any imported files larger than this size should be extracted
minSize: 20000.// Indicates how many bytes a packet is larger than or equal to minSize
// In most cases, if maxSize is set, minSize and maxSize will be the same
maxSize: 20000.// The number of times a package has been introduced depends on how many times it needs to be removed
minChunks: 1.// cacheGroups means that all module outputs are stored in a cache and then performed together
cacheGroups: {
// The key can be arbitrary. It is just a placeholder here
// Value is a configuration object
vender: {
// Regular to match the corresponding module path
test: /[\\/]node_modules[\\/]/.// Output file name The output file is output in the form of output file name -hash value.js
// name: "vender",
// filename specifies the output filename. Unlike name, filename can use placeholder
filename: 'vender_[id].js'.// The priority is set to a negative value
priority: -10
},
default: {
minChunks: 2.filename: "common_[id].js".priority: -20}}}}Copy the code
Dynamic import
When we touch a module we want to load it while the code is running (for example, when a condition is true)
We don’t know for sure that the code in this module will be used, so it’s best to split it into a separate JS file
This ensures that the browser does not need to load and process the JS code for the file when the content is not used
At this point we can use dynamic imports
Whenever the code is imported asynchronously, WebPack separates the code regardless of the size of the file
import('./main').then(res= > console.log(res))
Copy the code
Name the file to be dynamically imported
Because dynamic imports are always packaged into separate files, they are not configured in cacheGroups
So the name of it is usually in the output, by the chunkFilename attribute
output: {
path: path.resolve(__dirname, '.. /dist'),
// When synchronizing modules, it is usually called bundle
filename: '[name].bundle.js'.// If it is a dynamic import, it is usually named chunk
chunkFilename: '[name].chunk.js'
}
Copy the code
You’ll notice that by default we get [name] the same as the name of the ID
If we want to change the value of name, we can do so with magic comments
import(/* webpackChunkName: 'foo' */'./foo').then(res= > console.log(res))
Copy the code
At this point, the packaged module name will be foo.chunk.js
Remove the detach of comments
In production mode, after the packaged code, some comments information will be extracted by default to form the corresponding TXT file
const TerserPlugin = require('terser-webpack-plugin')
optimization: {
// Compression configuration
minimizer: [
new TerserPlugin({
// Close the detach of comments
extractComments: false,}})]Copy the code
chunkIds
The optimization. ChunkIds configuration is used to tell WebPack what algorithm is used to generate the id of the module
value | instructions |
---|---|
natural | Use the ids in numeric order Such as 2. Bundle. Js, 3. Bundle. Js Not recommended, because if you remove a module, the natural number of each module will change Unfavorable cache operation for modules |
named | Default value under evelopment, a readable name id; (Recommended during development) |
deterministic | Deterministic, based on internal algorithms that generate short numeric ids that do not change between compilations (recommended during packing) |