Three long articles will help you unlock Webpack. Hopefully, after reading these three articles, you will have a clearer understanding of the configuration of Webpack.
This is the second article, and if you haven’t already read the Webpack Series (Basics), it’s recommended that you continue reading this article.
This article will introduce more WebPack configurations, so if there are any bugs, please point them out in the comments section and I will fix them as soon as possible. The Webpack optimization section is in the next article.
We recommend you to refer to this article step by step configuration, do not always think about what to find the best configuration, after mastering, according to their own needs to configure, is the best configuration.
The corresponding project address (used in writing this article) is for your reference: github.com/YvetteLau/w…
1. Static resource copy
Sometimes, we need to use existing JS files, CSS files (native files), but do not need webpack compilation. For example, we introduced js or CSS files in the public directory in public/index.html. At this point, if you package directly, then after the build, there will be no corresponding JS/CSS.
Public Directory structure
├ ─ ─ public │ ├ ─ ─ config. Js │ ├ ─ ─ index. The HTML │ ├ ─ ─ js │ │ ├ ─ ─ base. Js │ │ └ ─ ─ other. Js │ └ ─ ─ the login. The HTMLCopy the code
Now we’ve introduced./js/base.js in index.html.
<! -- index.html -->
<script src="./js/base.js"></script>
Copy the code
NPM run dev can’t find the resource file.
For this problem, we can manually copy it to the build directory, and then configure the CleanWebpackPlugin with care not to empty the corresponding files or folders, but if the static file is modified from time to time, then relying on manual copy can easily cause problems.
Don’t put too much faith in your memory and rely on manual copying. Most of us have forgotten copying at some point in our lives.
Fortunately for those of us with short memories and slackers, WebPack provides the handy CopyWebpackPlugin, which simply copies a single file or entire directory to the build directory.
First install dependencies:
npm install copy-webpack-plugin -D
Copy the code
Modify the configuration (currently, all you need to do is copy the public/js directory to the dist/js directory) :
//webpack.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
/ /...
plugins: [
new CopyWebpackPlugin([
{
from: 'public/js/*.js'.to: path.resolve(__dirname, 'dist'.'js'),
flatten: true,},// You can continue to configure other files to be copied])]}Copy the code
In this case, run the NPM run dev command again, and the error message disappears.
If flatten is set to true, it will only copy files, not all folder paths. If flatten is set to true, it will not copy all folder paths.
In addition, CopyWebpackPlugin provides ignore if we want to copy many files in a directory but want to filter out one or more files.
//webpack.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
/ /...
plugins: [
new CopyWebpackPlugin([
{
from: 'public/js/*.js'.to: path.resolve(__dirname, 'dist'.'js'),
flatten: true,}, {ignore: ['other.js']]}})Copy the code
For example, here we ignore the other.js file in the js directory and use the NPM run build. You can see that the other.js file does not appear under dist/js. The CopyWebpackPlugin also provides many other parameters, so if the current configuration doesn’t meet your requirements, you can consult the documentation to modify the configuration further.
2.ProvidePlugin
The ProvidePlugin, in my opinion, is for lazy people, but don’t overuse it because global variables are bad stuff. The ProvidePlugin can be used anywhere in a project without import or require.
ProvidePlugin is a built-in plug-in for WebPack and can be used as follows:
new webpack.ProvidePlugin({
identifier1: 'module1'.identifier2: ['module2'.'property2']});Copy the code
The default path to find is the current folder./** and node_modules, of course, you can specify the full path.
When using React, you need to introduce React in every file, or else you’ll get it wrong. Jquery, lodash, jquery, lodash, jquery, Lodash, jquery, Lodash, jquery, LoDash
const webpack = require('webpack');
module.exports = {
/ /...
plugins: [
new webpack.ProvidePlugin({
React: 'react'.Component: ['react'.'Component'].Vue: ['vue/dist/vue.esm.js'.'default'].$: 'jquery'._map: ['lodash'.'map']]}})Copy the code
This allows you to use $and _map as you like in your project, and write React components without the need to import React and Component. You can also configure the React Hooks here if you want.
In addition, the configuration of Vue is followed by a default. This is because the export default is used in Vue. Esm. js. React uses module.exports, so don’t write default.
Also, if your project has ESLint enabled, remember to modify the esLint configuration file and add the following configuration:
{
"globals": {
"React": true."Vue": true, / /... }}Copy the code
Of course, there is a certain amount of laziness. If you configure a lot of global variables, you may end up in trouble. Be responsible for your own global variables.
3. The CSS to pull away
CSS packaging as we already said, however, in some cases, we may have a need to pull away the CSS, the CSS file individually packaged, it may be that the packaged into a JS file is too large, affect the loading speed, it is possible that in order to cache (for example, only JS changes), and is likely to be “I am” : I can pull away if I want. It’s nobody’s business.
Whatever your reason for pulling out of CSS, if you need it, we can do it.
Install loader:
npm install mini-css-extract-plugin -D
Copy the code
Mini-css-extract-plugin and extract-text-webpack-plugin:
- Asynchronous loading
- No double compilation (better performance)
- Easier to use
- Only for CSS
Modify our configuration file:
//webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].css'
// I prefer to keep CSS files in a separate directory
//publicPath:'.. /' // If the publicPath of your output is set to the relative path of './', remember to specify publicPath here if the CSS file is placed in a separate directory})].module: {
rules: [{test: /\.(le|c)ss$/.use: [
MiniCssExtractPlugin.loader, // Replace the previous style-loader
'css-loader', {
loader: 'postcss-loader'.options: {
plugins: function () {
return [
require('autoprefixer') ({"overrideBrowserslist": [
"defaults"]})]}}},'less-loader'].exclude: /node_modules/}}}]Copy the code
Now let’s recompile: NPM run build with the following directory structure:
. ├ ─ ─ dist │ ├ ─ ─ assets │ │ ├ ─ ─ alita_e09b5c. JPG │ │ └ ─ ─ thor_e09b5c. Jpeg │ ├ ─ ─ CSS │ │ ├ ─ ─ index. The CSS │ │ └ ─ ─ ├─ ├─ ├.class.txt, ├─ ├.class.txt, class.txt, class.txt, class.txt, class.txt, class.txt, class.txt, class.txtCopy the code
Create a new.browserslistrc file (.browserslistrc) in the root directory (you can change it to another configuration for your own project) :
Last 2 version > 0.25% not deadCopy the code
Modify the webpack. Config. Js:
//webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
/ /...
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].css'})].module: {
rules: [{test: /\.(c|le)ss$/.use: [
MiniCssExtractPlugin.loader,
'css-loader', {
loader: 'postcss-loader'.options: {
plugins: function () {
return [
require('autoprefixer'()]}}},'less-loader'].exclude: /node_modules/}}},]Copy the code
To test if your.browserlistrc is in effect, you can simply change the file content to the last 1 Chrome versions and compare the build results before and after the modification.
See more [Browserslistrc] configuration items (github.com/browserslis…)
For more configuration items, see the Mini-CSs-extract-Plugin
Compress the extracted CSS files
Optimization-css-assets-webpack-plugin (optimization-css-assets-webpack-plugin, optimization-CSs-assets-webpack-plugin)
npm install optimize-css-assets-webpack-plugin -D
Copy the code
Modify the WebPack configuration:
//webpack.config.js
const OptimizeCssPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
entry: './src/index.js'.//....
plugins: [
new OptimizeCssPlugin()
],
}
Copy the code
OptimizeCssPlugin: OptimizeCssPlugin: OptimizeCssPlugin: OptimizeCssPlugin: OptimizeCssPlugin: OptimizeCssPlugin: OptimizeCssPlugin: OptimizeCssPlugin: OptimizeCssPlugin: OptimizeCssPlugin: OptimizeCssPlugin: OptimizeCssPlugin: OptimizeCssPlugin: OptimizeCssPlugin: OptimizeCssPlugin Put it in webpack.config.prod.js later.
After the configuration, when testing, I found that after pulling out and modifying the CSS file, the first page would refresh, but the second page would not refresh – well, I don’t need to pull out CSS in my normal business, and this problem was left on the back of my mind for a few days (or more accurately, forgotten).
On March 8, modify the article again, just to see the MiniCssExtractPlugin. Loader corresponding option Settings, we modify the corresponding rule again.
module.exports = {
rules: [{test: /\.(c|le)ss$/.use: [{loader: MiniCssExtractPlugin.loader,
options: {
hmr: isDev,
reloadAll: true,}},/ /...].exclude: /node_modules/}}]Copy the code
4. Load as needed
Most of the time we don’t need to load all the JS files at once, but should load the required code at different stages. Webpack has a powerful built-in feature for splitting code to load on demand.
For example, if we need to use the code in the corresponding JS file after clicking a button, we need to use the import() syntax:
document.getElementById('btn').onclick = function() {
import('./handle').then(fn= > fn.default());
}
Copy the code
Import () syntax, requires @babel/plugin-syntax-dynamic-import plug-in support, But since the current @babel/preset-env preset already includes @babel/plugin-syntax-dynamic-import, we don’t need a separate installation and configuration.
Build directly from NPM run build and the result is as follows:
When Webpack encounters syntax like import(****), it does this:
- Create a new one with **** as the entry point
Chunk
- When the code executes to
import
Is loaded before the statement is loadedChunk
The corresponding file (e.g. 1.bundle.8bf4dc.js here)
You can check the file loading situation on the Tab page of Network in the console of the browser. Only after clicking, the corresponding JS will be loaded.
5. Hot update
- The first configuration
devServer
的hot
为true
- And in the
plugins
addnew webpack.HotModuleReplacementPlugin()
//webpack.config.js
const webpack = require('webpack');
module.exports = {
//....
devServer: {
hot: true
},
plugins: [
new webpack.HotModuleReplacementPlugin() // Hot update plugin]}Copy the code
After we configure HotModuleReplacementPlugin, will find that, at this time, we modify the code, is still the entire page will be refreshed. You don’t want the entire page to refresh, you also need to modify the entry file:
- Add:
if(module && module.hot) {
module.hot.accept()
}
Copy the code
At this point, modifying the code will not cause the entire page to refresh.
6. Multi-page application packaging
Sometimes, our application is not necessarily a single page application, but a multi-page application, so how to use WebPack to package. To make the generated directory look clean, do not generate a separate map file.
//webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/index.js'.login: './src/login.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[hash:6].js'
},
/ /...
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'.filename: 'index.html' // The packaged file name
}),
new HtmlWebpackPlugin({
template: './public/login.html'.filename: 'login.html' // The packaged file name]}}),Copy the code
If you need to configure multiple htmlwebpackplugins, the filename field cannot be default. Otherwise, the index.html field will be generated by default. If you want the HTML filename to have a hash, you can simply change the fliename field. Filename: ‘ ‘login. [hash: 6]. HTML.
The generated directory is as follows:
. ├ ─ ─ dist │ ├ ─ ─ 2.463 CCF. Js │ ├ ─ ─ assets │ │ └ ─ ─ thor_e09b5c. Jpeg │ ├ ─ ─ CSS │ │ ├ ─ ─ index. The CSS │ │ └ ─ ─ the login. The CSS │ ├ ─ ─ ├─ ├─ ├─ ├─ login.txt ├.txt ├─ login.txtCopy the code
This might seem OK, but if you look at index.html and login.html, you’ll see that both of them introduce index.f7d21a.js and login.f7d21a.js, which is usually not what we want, we want, Only index.f7d21a.js is imported into index.html and login.html is imported into login.f7d21a.js.
The HtmlWebpackPlugin provides a value of chunks that can accept an array. Configuring this parameter only introduces the js specified in the array into the HTML file. If you need to introduce multiple JS files, only a few of which do not want to, you can also specify excludeChunks. It takes an array.
//webpack.config.js
module.exports = {
/ /...
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'.filename: 'index.html'.// The packaged file name
chunks: ['index']}),new HtmlWebpackPlugin({
template: './public/login.html'.filename: 'login.html'.// The packaged file name
chunks: ['login']})]}Copy the code
After executing the NPM run build, you can see that only the INDEX JS file is introduced in index. HTML and only the login JS file is introduced in login. HTML, as expected.
7. Resolve configuration
Resolve configures how WebPack finds files for modules. Webpack has built-in JavaScript modular syntax parsing capabilities, which are found by default using rules agreed in the modular standard, but you can modify the default rules to suit your needs.
- modules
Resolve. modules configures the directory in which webpack will look for third-party modules. By default, node_modules is only used. This can be simplified by configuring resolve.modules.
//webpack.config.js
module.exports = {
//....
resolve: {
modules: ['./src/components'.'node_modules'] // Search from left to right}}Copy the code
After this configuration, we import Dialog from ‘Dialog’ and look for./ SRC /components/ Dialog, no longer using the relative path import. If you can’t find it under./ SRC /components, you’ll look for it under node_modules.
- alias
The resolve.alias configuration item maps the original import path to a new import path by alias, for example:
//webpack.config.js
module.exports = {
//....
resolve: {
alias: {
'react-native': '@my/react-native-web' // The package name is arbitrary}}}Copy the code
For example, we have a dependency on @my/react-native web to implement the react-native web. Our code generally looks like this:
import { View, ListView, StyleSheet, Animated } from 'react-native';
Copy the code
After the alias is configured, the system searches for the corresponding dependency from @my/ React-native Web when transferring to the Web.
Of course, if a dependency’s name is too long, you can also give it a shorter alias, which is easier to use, especially for packages with scope.
- extensions
Web. Js,.wx.js, for example, in a web project, we want to find.web. Js first, if not, then.js. We can configure it like this:
//webpack.config.js
module.exports = {
//....
resolve: {
extensions: ['web.js'.'.js'] // You can also configure.json,.css}}Copy the code
First look for.. /dialog.web.js, if not present, look for.. / dialog. Js. This is very useful in multi-terminal code where you would otherwise have to import files for different platforms (at the expense of speed).
import dialog from '.. /dialog';
Copy the code
If the import statement does not have a file suffix, it will automatically add the suffix configured in the extensions to try to access the file. Therefore, put the suffix in front of the file and make the array not too long to reduce the number of attempts. If extensions are not configured, only the appropriate JS file will be found by default.
- enforceExtension
If resolve.enforceExtension is configured to true, import statements cannot default file suffixes.
- mainFields
Some third-party modules provide multiple pieces of code, such as bootstrap. You can view the package.json file of bootstrap:
{
"style": "dist/css/bootstrap.css"."sass": "scss/bootstrap.scss"."main": "dist/js/bootstrap",}Copy the code
Resolve. MainFields default configuration is [‘browser’, ‘main’], which first looks for the brower field in the corresponding dependency package.json, and if not, looks for the main field.
For example: import ‘bootstrap’ By default, find the file specified in the main field of the corresponding dependency package.json, dist/js/bootstrap.
If we want import ‘bootsrap’ to find CSS files by default, we can configure resolve.mainFields to:
//webpack.config.js
module.exports = {
//....
resolve: {
mainFields: ['style'.'main']}}Copy the code
8. Differentiate between environments
So far, the configuration of our webpack is defined in webpack.config.js. For the case that we need to distinguish the development environment from the production environment, we distinguish the configuration according to process.env.node_env. However, this is not a good idea if there are multiple places in the configuration file that need to differentiate the configuration of the environment.
It is better to create multiple configuration files such as webpack.base.js, webpack.dev.js, and webpack.prod.js.
webpack.base.js
Define common configurationswebpack.dev.js
: Defines the configuration of the development environmentwebpack.prod.js
: Defines the configuration of the production environment
Webpack-merge is designed for Webpacks and provides a merge function that joins arrays and merges objects.
npm install webpack-merge -D
Copy the code
const merge = require('webpack-merge');
merge({
devtool: 'cheap-module-eval-source-map'.module: {
rules: [{a: 1}},plugins: [1.2.3] {},devtool: 'none'.mode: "production".module: {
rules: [{a: 2},
{b: 1}},plugins: [4.5.6]});// The result of the merge is
{
devtool: 'none'.mode: "production".module: {
rules: [{a: 1},
{a: 2},
{b: 1}},plugins: [1.2.3.4.5.6]}Copy the code
Webpack.config.base. js is the general webpack configuration, using webpack.config.dev.js as an example, as follows:
//webpack.config.dev.js
const merge = require('webpack-merge');
const baseWebpackConfig = require('./webpack.config.base');
module.exports = merge(baseWebpackConfig, {
mode: 'development'
/ /... Some other configuration
});
Copy the code
Then modify our package.json to specify the corresponding config file:
//package.json
{
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --config=webpack.config.dev.js",
"build": "cross-env NODE_ENV=production webpack --config=webpack.config.prod.js"
},
}
Copy the code
Smart merges the same matching rules when merging loaders. The webpack-Merge documentation provides a detailed example.
9. Define environment variables
Most of the time, we use pre-release or local domain names in development and online domain names in production. We can define environment variables in Webpack and use them in code.
Use the WebPack built-in plug-in DefinePlugin to define environment variables.
Each key in DefinePlugin is an identifier.
- if
value
It’s a string. It’s going to be treated ascode
fragment - if
value
It’s not a string, it’s gonna bestringify
- if
value
Is an object, the normal object definition - if
key
There aretypeof
It only applies totypeof
Calls to define
//webpack.config.dev.js
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
DEV: JSON.stringify('dev'), / / string
FLAG: 'true' //FLAG is a Boolean}})]Copy the code
//index.js
if(DEV === 'dev') {
// Development environment
}else {
// Production environment
}
Copy the code
10. Use Webpack to solve cross-domain problems
Assuming that the front-end is at port 3000 and the server is at port 4000, we implement cross-domain through webPack configuration.
First, we create a server.js locally:
let express = require('express');
let app = express();
app.get('/api/user', (req, res) => {
res.json({name: 'Liu Xiaoxi'});
});
app.listen(4000);
Copy the code
Execute code (run code), now we can access to this interface in your browser: http://localhost:4000/api/user.
Request/API /user in index.js and modify index.js as follows:
// Need to forward localhost:3000 to localhost:4000 (server) port
fetch("/api/user")
.then(response= > response.json())
.then(data= > console.log(data))
.catch(err= > console.log(err));
Copy the code
We want to configure proxy to access the interface of 4000.
Configure the agent
Modify the WebPack configuration:
//webpack.config.js
module.exports = {
/ /...
devServer: {
proxy: {
"/api": "http://localhost:4000"}}}Copy the code
Rerunning NPM run dev, you can see that the console prints {name: “Liu Xiaoxi “}, implementing cross-domain.
In most cases, the backend provides interfaces that do not contain/apis, i.e., /user, /info, /list, etc. When configuring the proxy, it is impossible to list every API.
Modify our server code and re-execute it.
//server.js
let express = require('express');
let app = express();
app.get('/user', (req, res) => {
res.json({name: 'Liu Xiaoxi'});
});
app.listen(4000);
Copy the code
Although the back-end interface does not contain/API, we still start with/API when requesting the back-end interface. When configuring the proxy, we remove/API and modify the configuration:
//webpack.config.js
module.exports = {
/ /...
devServer: {
proxy: {
'/api': {
target: 'http://localhost:4000'.pathRewrite: {
'/api': ' '
}
}
}
}
}
Copy the code
Re-execute NPM run dev and visit http://localhost:3000/ in the browser. {name: “Liu Xiaoxi “} is also printed in the console. Cross-domain success.
11. Front-end simulation data
Simple data simulation
module.exports = {
devServer: {
before(app) {
app.get('/user', (req, res) => {
res.json({name: 'Liu Xiaoxi'})})}}}Copy the code
Request the /user interface directly in SRC /index.js.
fetch("user")
.then(response= > response.json())
.then(data= > console.log(data))
.catch(err= > console.log(err));
Copy the code
Use mocker-API mock data interfaces
Mocker-api creates mock apis for REST apis. It can be useful when testing applications without an actual REST API server.
- Install the mocker – API:
npm install mocker-api -D
Copy the code
- Create a mock folder in your project and create a mocker.js. file like this:
module.exports = {
'GET /user': {name: 'Liu Xiaoxi'},
'POST /login/account': (req, res) = > {
const { password, username } = req.body
if (password === '888888' && username === 'admin') {
return res.send({
status: 'ok'.code: 0.token: 'sdfsdfsdfdsf'.data: { id: 1.name: 'Liu Xiaoxi'}})}else {
return res.send({ status: 'error'.code: 403})}}}Copy the code
- Modify the
webpack.config.base.js
:
const apiMocker = require('mocker-api');
module.export = {
/ /...
devServer: {
before(app){
apiMocker(app, path.resolve('./mock/mocker.js'))}}}Copy the code
This allows us to request mock data directly in our code as if we were requesting a back-end interface.
- restart
npm run dev
As you can see, the console prints successfully{name: 'Liu Xiaoxi '}
- Let’s modify it
src/index.js
To check whether the POST interface is successful
//src/index.js
fetch("/login/account", {
method: "POST".headers: {
'Accept': 'application/json'.'Content-Type': 'application/json'
},
body: JSON.stringify({
username: "admin".password: "888888"
})
})
.then(response= > response.json())
.then(data= > console.log(data))
.catch(err= > console.log(err));
Copy the code
You can see the success data returned by the interface in the console.
Advanced article to end here, the last article is optimization, next week a small bench and melon seeds to about.
The last
If this article has helped you, give it a thumbs up.
Unlock the Webpack series in depth
Follow public account
Join a technical networking group
Reference:
- mini-css-extract-plugin
- html-webpack-tags-plugin
- CopyWebpackPlugin
- webpack-merge
- How to mock data in Webpack
- Resolve Webpack summary
- DefinePlugin for a useful Webpack plug-in
- Talk about webpack