Webpack Dev Server
It integrates the functions of automatic compilation and automatic refresh of browser. The webpack dev server does not write to any output files after compilation, but keeps the bundle files in memory and then serves them to the server.
yarn add webpack-dev-server --dev
const path = require("path"); const { CleanWebpackPlugin } = require("clean-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const CopyWebpackPlugin = require("copy-webpack-plugin"); module.exports = { mode: "development", entry: "./src/main.js", output: { filename: "bundle.js", path: path.join(__dirname, "dist"), }, module: { rules: [ { test: /.css$/, use: ["style-loader", "css-loader"], }, { test: /.png$/, use: { loader: "url-loader", options: { limit: 10 * 1024, }, }, }, ], }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ title: "Ideal Webpack Develop Env", meta: { viewport: "width=device-width", }, template: "./src/index.html", }), // It is better not to use this plug-in in the development stage and copy static resource files frequently // new CopyWebpackPlugin({ // patterns: ["public"], // }), ], devServer: { // Specify additional static resource file path contentBase: ["./public"], proxy: { "/api": { // http://localhost:8080/api/users -> https://api.github.com/api/users target: "https://api.github.com", // http://localhost:8080/api/users -> https://api.github.com/users pathRewrite: { "^/api": "", }, }, }, }, };
yarn webpack serve --open
Source Map
A Source map is an information file that stores location information. That is, each position of the converted code corresponds to the position before conversion.
With it, when an error occurs, the debugger will directly display the original code instead of the converted code. This undoubtedly brings great convenience to developers.
To enable Source map, you only need to add / / # sourceMappingURL=/path/to/file.js.map at the end of the converted code
source map in Webpack
const path = require("path"); module.exports = { mode: "development", entry: "./src/main.js", output: { filename: "bundle.js", path: path.join(__dirname, "dist"), }, devtool: "source-map", };
Common mode:
- eval: it can only locate files without generating source map files. It is fast to build and cannot know row and column information
- Eval source map: you can locate files and row and column information
- Eval heap source map: only line information can be located, and the code is transformed by loader
- Eval cheap module source map: source code of development environment
HMR(Hot Module Replacement)
HMR allows all types of modules to be updated at run time without a full refresh
const path = require("path"); const { CleanWebpackPlugin } = require("clean-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const CopyWebpackPlugin = require("copy-webpack-plugin"); module.exports = { mode: "development", entry: "./src/main.js", output: { filename: "bundle.js", path: path.join(__dirname, "dist"), }, module: { rules: [ { test: /.css$/, use: ["style-loader", "css-loader"], }, { test: /.png$/, use: { loader: "url-loader", options: { limit: 10 * 1024, }, }, }, ], }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ title: "Ideal Webpack Develop Env", meta: { viewport: "width=device-width", }, template: "./src/index.html", }), ], devServer: { hot: true, // hotOnly: true / / only use HMR, not fallback to live reloading }, devtool: "source-map", };
Because css has uniform rules, you only need to override the replacement style, and style loader has implemented the hot replacement of HMR. js files are not complex and diverse, and there are no unified rules, so we need to implement them according to the actual situation
import "./main.css"; import createEditor from "./editor"; const editor = createEditor(); document.body.appendChild(editor); // ================================================================ // HMR manual processing module hot update // Don't worry about the redundancy of these codes in the production environment, because after packaging through webpack, // All the code will be removed, which is only used in the development phase if (module.hot) { let hotEditor = editor; module.hot.accept("./editor.js", () => { // This function is automatically executed when editor.js is updated // Temporary record editor content const value = hotEditor.innerHTML; // Remove pre update elements document.body.removeChild(hotEditor); // Create a new editor // At this point, createEditor is the updated function hotEditor = createEditor(); // Restore editor content hotEditor.innerHTML = value; // Append to page document.body.appendChild(hotEditor); }); module.hot.accept("./better.png", () => { // Execute after better.png is updated // Overriding the setting src triggers the picture element to reload, thereby locally updating the picture img.src = background; }); // The style loader automatically processes the updated styles internally, so there is no need to manually process the style module }
Configuration in different environments
Create different configurations for different environments and use webpack merge to merge the same configurations
webpack.common.js
const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { entry: "./src/main.js", output: { filename: "js/bundle.js", }, module: { rules: [ { test: /\.css$/, use: ["style-loader", "css-loader"], }, { test: /\.(png|jpe?g|gif)$/, use: { loader: "file-loader", options: { outputPath: "img", name: "[name].[ext]", }, }, }, ], }, plugins: [ new HtmlWebpackPlugin({ title: "Webpack Tutorial", template: "./src/index.html", }), ], };
webpack.dev.js
const webpack = require("webpack"); const { merge } = require("webpack-merge"); const common = require("./webpack.common"); module.exports = merge(common, { mode: "development", devtool: "source-map", devServer: { hot: true, contentBase: "public", }, plugins: [new webpack.HotModuleReplacementPlugin()], });
webpack.prod.js
const { merge } = require("webpack-merge"); const { CleanWebpackPlugin } = require("clean-webpack-plugin"); const CopyWebpackPlugin = require("copy-webpack-plugin"); const common = require("./webpack.common"); module.exports = merge(common, { mode: "production", plugins: [new CleanWebpackPlugin(), new CopyWebpackPlugin(["public"])], });
Execute package command
yarn webpack --config webpack.dev.js
DefinePlugin
DefinePlugin allows you to replace variables in your code with other values or expressions at compile time. This is very useful when different operations need to be carried out according to the development mode and production mode. For example, replace the domain name
new webpack.DefinePlugin({ // Value requires a snippet of code API_BASE_URL: JSON.stringify("https://api.example.com"), }),
Note that since this plug-in will directly replace the text, the provided value must contain an actual quotation mark in the string itself. In general, you can replace quotation marks with something like 'production', or use JSON.stringify('production ') directly.
Tree Shaking
Describes removing unreferenced code from a JavaScript context. It is enabled by default in production mode.
const path = require("path"); const webpack = require("webpack"); const { CleanWebpackPlugin } = require("clean-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { mode: "none", entry: "./src/main.js", output: { filename: "bundle.js", path: path.join(__dirname, "dist"), }, optimization: { // The module exports only the used members usedExports: true, // Compressed output results minimize: true, }, };
sideEffects
Using the "sideEffects" attribute of package.json as a tag, the compiler is prompted to indicate which files in the project are "pure (pure ES2015 module)", so that unused parts in the file can be safely deleted.
{ "sideEffects": false }
If your code does have some side effects, you can provide an array instead:
{ "sideEffects": ["./src/some-side-effectful-file.js"] }
"Side effect" is defined as code that performs special behavior during import, rather than exposing only one or more exports. For example, polyfill affects the global scope and usually does not provide export.
Code subcontracting
Multi entry packaging
It is applicable to multi page packaging. One page corresponds to one packaging entry to extract the public part
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { mode: "none", entry: { index: "./src/index.js", album: "./src/album.js", }, output: { filename: "[name].bundle.js", }, optimization: { splitChunks: { // Automatically extract all common modules to a separate bundle chunks: "all", }, }, module: { rules: [ { test: /\.css$/, use: ["style-loader", "css-loader"], }, ], }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ title: "Multi Entry", template: "./src/index.html", filename: "index.html", chunks: ["index"], }), new HtmlWebpackPlugin({ title: "Multi Entry", template: "./src/album.html", filename: "album.html", chunks: ["album"], }), ], };
Dynamic import
Dynamically imported modules will be automatically subcontracted to realize on-demand loading
Magic note: define the name of the subcontracting bundle
// import posts from './posts/posts' // import album from './album/album' const render = () => { const hash = window.location.hash || "#posts"; const mainElement = document.querySelector(".main"); mainElement.innerHTML = ""; if (hash === "#posts") { // mainElement.appendChild(posts()) import(/* webpackChunkName: 'components' */ "./posts/posts").then( ({ default: posts }) => { mainElement.appendChild(posts()); } ); } else if (hash === "#album") { // mainElement.appendChild(album()) import(/* webpackChunkName: 'components' */ "./album/album").then( ({ default: album }) => { mainElement.appendChild(album()); } ); } }; render(); window.addEventListener("hashchange", render);
CSS file compression subcontracting
yarn add terser-webpack-plugin optimize-css-assets-webpack-plugin mini-css-extract-plugin --dev
- Terser webpack plugin: this plugin uses terser to compress JavaScript
- Optimize CSS assets webpack plugin: optimize and compress CSS using cssnano
- Mini CSS extract plugin: this plugin will extract CSS into a separate file, create a CSS file for each JS file containing CSS, and support on-demand loading of CSS and SourceMaps
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin"); const TerserWebpackPlugin = require("terser-webpack-plugin"); module.exports = { mode: "none", entry: { main: "./src/index.js", }, output: { filename: "[name].bundle.js", }, optimization: { minimizer: [ // Declaring an array will make webpack think that we need custom compression, so we need to declare the compression of js files ourselves new TerserWebpackPlugin(), new OptimizeCssAssetsWebpackPlugin(), ], }, module: { rules: [ { test: /\.css$/, use: [ // 'style loader ', / / inject styles through style tags MiniCssExtractPlugin.loader, "css-loader", ], }, ], }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ title: "Dynamic import", template: "./src/index.html", filename: "index.html", }), new MiniCssExtractPlugin(), ], };
Hash file name
File name dependent hash cache.
At the project level, any change to the project will change
output: { filename: "[name]-[hash].js", },
At the chunk level, changes to the same chunk will reference changes to the same chunk
output: { filename: "[name]-[chunkhash].js", },
At the file level, different files have different hash values, and the hash length is 8 bits
output: { filename: "[name]-[contenthash:8].js", },