preface
This long article is to learn cheng Liufeng teacher opened “play webpack” column practice notes, and column is not the same, my actual combat source code is based on Webpack5, its configuration and source code implementation and Webpack4 have many different places, interested students can combine with me in the above released source code warehouse for learning, I believe there will be no small harvest.
After reading this long article, you will learn:
- Flexible configuration of WebPack according to project requirements.
- Understand the principles of Tree Shaking, Scope, etc.
- It can package multi-page application, component library and base library, SSR, etc.
- Write a maintainable Webpack configuration, control code quality with unit tests, smoke tests, and so on, and use Travis for continuous integration.
- Know how to optimize build speed and build resource volume.
- Master webpack packaging principle through source code
- Write loader and plugin.
I put the source code in my repository, which can be read against the document. In addition, due to the word limit, I can only be divided into two articles. For a better reading experience, you can go to my blog. Source address: play webpack
I met webpack
Configuration file: webpack.config.js
Webpack configuration consists of:
Install webpack:
yarn add webpack webpack-cli -D
Copy the code
Execute webpack from the command line:
./node_modules/.bin/webpack
Copy the code
Run webpack with NPM script:
Principle: Create a soft connection in the node_modules/. Bin directory
yarn run build
Copy the code
Basic usage of Webpack
Entry of core concepts
Specifies the entry to the WebPack package.
Dependency graph (build mechanism) : WebPack treats all resources as modules, starting from the entry file, recursively parses the dependency modules to form a dependency tree, and when the recursion is complete, outputs the built resources.
Method of use
Single entry: The value of entry is a string
module.exports = {
entry: './src/index.js'};Copy the code
Multiple entry: The value of entry is an object
module.exports = {
entry: {
main: './src/index.js',}};Copy the code
Output of core concepts
Tell WebPack where to put the built resources on disk
Method of use
Single entry:
module.exports = {
output: {
filename: 'bundle.js'.path: path.resolve(__dirname, '/dist'),}};Copy the code
Multiple entry:
Ensure that file names are unique through placeholders
module.exports = {
output: {
filename: '[name].js'.path: path.resolve(__dirname, '/dist'),}};Copy the code
Core concepts loader
Webpack supports only JS and JSON file types by default. You can configure parsing rules for other file types by using Loader, so that WebPack can add other file types to the dependency graph.
Is itself a function that takes the source file as an argument and returns the result of the transformation
Method of use
module.exports = {
module: {
rules: [{ test: /\.txt/, use: 'raw-loader'}],}};/ / configuration items
module.exports = {
module: {
rules: [{test: /\.txt/,
use: {
loader: 'raw-loader'.options: {},},},],},};Copy the code
Plugin for core concepts
Plugin is used for bundle optimization, resource management, and environment variable injection throughout the build process.
Mode of core concepts
Mode specifies the build environment: production/development/none.
Parse ES6 and JSX
Parsing es6
With babel-loader, the configuration file for Babel is babel.config.json
{
"presets": ["@babel/preset-env"]."plugins": ["@babel/proposal-class-properties"]}Copy the code
Presets are collections of plugins that represent presets that are specific to a particular function.
Install dependencies
yarn add @babel/core @babel/preset-env babel-loader -D
Copy the code
Configure the loader
module.exports = {
module: {
rules: [{test: /\.js$/,
use: {
loader: 'babel-loader',},},],},};Copy the code
Parsing JSX
Install dependencies
yarn add react react-dom @babel/preset-react
Copy the code
{
"presets": ["@babel/preset-env"."@babel/preset-react"]}Copy the code
Parsing the CSS/less sass
Parsing the CSS
Css-loader is used to load. CSS files and convert them into CommonJS objects
Style-loader injects the style into the head via the
module.exports = {
module: {
rules: [{test: /\.css$/,
use: ['style-loader'.'css-loader'],},],},};Copy the code
The loader parse rules are from right to left. That is, csS-loader is used to parse files and then the result is handed to style-loader.
Parsing the less
Install dependencies
yarn add less less-loader -D
Copy the code
module.exports = {
module: {
rules: [{test: /\.css$/,
use: ['style-loader'.'css-loader'.'less-loader'],},],},};Copy the code
Parse image and font resources
Using the file – loader
The installation file – loader
yarn add file-loader
Copy the code
module.exports = {
module: {
rules: [{test: /\.(png|jpg|jpeg|gif|woff|woff2|eot|ttf)$/,
use: ['file-loader'],},],},};Copy the code
Use url – loader
Small resources can be set to automatically base64
module.exports = {
module: {
rules: [{test: /\.(png|jpg|jpeg|gif|woff|woff2|eot|ttf)$/,
use: [{ loader: 'file-loader'.options: { limit: 10240}}],},],},};Copy the code
Use webpack5’s built-in asset
In versions earlier than Webpackage 5, the commonly used Loaders are as follows:
- Raw-loader processes modules into strings
- Url-loader can set the specified size of the resource. If the size is smaller than the specified size, the bundle is inlining.
- File-loader sends files to the output directory
In Webpackage 5, asset Modules replaces the loader above by adding four built-in types:
- Asset /resource was previously implemented by file-loader
- Asset /inline was previously implemented by url-loader
- Asset /source Exports the source code (as a string) of the resource, previously implemented by raw-loader
- Asset can automatically choose whether to export as a data URI or send a file directly, previously implemented by urL-loader.
When parsing image and font resources, you want to export the resources as data URIs within a limited size, while exceeding the size directly sends the files to the output directory, so use asset:
module.exports = {
module: {
rules: [{test: /\.(png|jpg|jpeg|gif|woff|woff2|eot|ttf)$/,
type: 'asset'.parser: {
dataUrlCondition: {
maxSize: 10 * 1024.// 10kb},},},],},};Copy the code
Listen for files and hot updates
watch
Listen for file changes, automatically build
{
"scripts": {
"watch": "webpack --watch"}}Copy the code
File listening principle analysis:
- Polling determines whether the last edit time of the file has changed
- Changes to a file are not immediately reported to listeners, but cached, etc
aggregateTimeout
Do not execute the build task until it expires
module.exports = {
watch: true.// Enable listening
watchOptions: {
// The default value is null and the listening folder is ignored to improve performance
ignored: /node_modules/.// Determine file changes by continuously asking the system whether the specified file has changed, once per second
poll: 1000.// wait until 300ms after the change
aggregateTimeout: 300,}};Copy the code
Hot update – WDS
webpack-dev-server:
- Not refreshing the browser
- Instead of exporting files, put them in memory (speed of build is a big advantage)
- Using HotModuleReplacementPlugin
Install dependencies:
yarn add webpack-dev-server -D
Copy the code
module.exports = {
devServer: {
contentBase: path.join(__dirname, 'dist'),
historyApiFallback: true.hot: true.open: true.quiet: true.port: 8082,}};Copy the code
{
"scripts": {
"serve": "webpack serve"}}Copy the code
Note: If there are multiple entries and only one HtmlWebpackPlugin is configured, multiple chunks will be inserted into the generated HTML and hot updates will not work properly.
Hot update – WDM
Webpack-dev-middleware: This is express middleware that allows WebPack to hand over files to a server, such as the next Express, which gives us more control.
Install dependencies
yarn add express webpack-dev-middleware -D
Copy the code
module.exports = {
output: {
publicPath: '/',}};Copy the code
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const app = express();
const config = require('./webpack.config');
const compiler = webpack(config);
app.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
})
);
app.listen(8081.function () {
console.log('server is running on port 8081');
});
Copy the code
Start the service Node server.js
Principle of thermal renewal
Concept:
- Webpack Compiler: Compiles JS into bundles
- Bundle Server: Provides a service that enables files to be accessed in the browser
- HMR Server: outputs hot updated files to the HMR Runtime
- HMR Runtime: this is injected into the bundle and used to update files (using WebSockets to communicate with the server)
- Bundle.js build artifacts
- Bootstrap: The source code is first compiled into bundle.js using the Webpack Compiler, and then submitted to the Bundle Server, where the browser can access the files.
- Change phase: WDS will detect whether the last edit time of the file has changed at regular intervals. Once detected, it will be added to the cache list, and so on
aggregateTimeout
Upon expiration, the cache list is sent to Webpack Compiler for compilation, the compiled code is sent to HMR Server, and the HRM Server informs HMR runtime change file (transmitted in JSON format). HMR Runtime then updates the response module code.
Strategy: file fingerprint chunkhash/contenthash/hash
File fingerprint: Output file name after packaging, usually used for version management, only updated file content, unupdated file fingerprint does not change, can still use the browser cache.
Common document fingerprints:
- Hash: Relates to the construction of the entire project. Whenever the project file is changed, the hash value for the entire project will change.
- The packaging phase has compile and compilation, and when Webpack starts, a compile object is created, and whenever the file changes, the compilation changes, and accordingly, the hash value changes.
- Page A changes, page B does not change, but the hash also changes.
- Chunkhash: Related to chunk (entry) packed by Webpack. Different entries will generate different chunkhash.
- Js generally uses chunkhash.
- Contenthash: Hash is defined based on the content of the file. Contenthash does not change the content of the file.
- The CSS generally uses contenthash.
Set the file fingerprint
It can only be used in production environments
Js file fingerprint setting
module.exports = {
output: {
filename: '[name]_[chunkhash:8].bundle.js',}};Copy the code
File fingerprint setting for CSS: Extract CSS into a file using mini-CSs-extract-Plugin, then set filename and use [contenthash].
If style-loader is used, the CSS will be injected into the head of the page and the file fingerprint cannot be set.
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
module: {
rules: [{test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],}, {test: /\.less$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'.'less-loader'],},],},plugins: [
new MiniCssExtractPlugin({
filename: '[name][contenthash:8].css',})]};Copy the code
Static resource files such as images fingerprint: name of file-loader. Hash is used.
module.exports = {
module: {
rules: [{test: /\.(png|jpg|jpeg|gif|woff|woff2|eot|ttf)$/,
use: [
{
loader: 'file-loader'.options: {
name: 'assets/[name][hash:8].[ext]',},},],},},};Copy the code
Split profile
Install webpack – merge:
yarn add webpack-merge -D
Copy the code
Split into three files and place them in the config directory:
- webpack.common.js
- webpack.dev.js
- webpack.prod.js
The configuration is as follows:
const path = require('path');
module.exports = {
entry: {
main: './src/index.js'.worker: './src/worker',},output: {
filename: '[name].js'.path: path.resolve(__dirname, '.. / '.'dist'),
publicPath: '/',},module: {
rules: [{test: /\.js$/,
use: {
loader: 'babel-loader',},},],},};Copy the code
const { merge } = require('webpack-merge');
const path = require('path');
const common = require('./webpack.common');
module.exports = merge(common, {
mode: 'development'.devServer: {
contentBase: path.join(__dirname, '.. /dist'),
historyApiFallback: true.hot: true.open: false.quiet: true.port: 8082,},module: {
rules: [{test: /\.(png|jpg|jpeg|gif|woff|woff2|eot|ttf)$/,
use: [
{
loader: 'file-loader'.options: {
name: 'assets/[name].[ext]',}}, {test: /\.css$/,
use: ['style-loader'.'css-loader'],}, {test: /\.less$/,
use: ['style-loader'.'css-loader'.'less-loader'],},],},],},});Copy the code
const { merge } = require('webpack-merge');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');
const common = require('./webpack.common');
module.exports = merge(common, {
mode: 'production'.output: {
filename: '[name]_[chunkhash:8].bundle.js'.path: path.resolve(__dirname, '.. / '.'dist'),
// publicPath: '/',
},
module: {
rules: [{test: /\.(png|jpg|jpeg|gif|woff|woff2|eot|ttf)$/,
use: [
{
loader: 'file-loader'.options: {
name: 'assets/[name]_[hash:8].[ext]',},},],}, {test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],}, {test: /\.less$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'.'less-loader'],},],},plugins: [
new MiniCssExtractPlugin({
filename: '[name]_[contenthash:8].css',})]});Copy the code
HTML, CSS, and Javascript code compression
Webpack turns on compression of Javascript code by default.
Compress CSS
Use csS-minimizer-webpack-plugin to be more precise in source maps and Assets than optimizer-CSS-assets-webpack-plugin, allowing caching and parallelism.
Install dependencies:
yarn add css-minimizer-webpack-plugin -D
Copy the code
Configuration:
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
optimization: {
minimize: true./ / '... 'can inherit the default compression configuration
minimizer: [new CssMinimizerPlugin(), '... '],}};Copy the code
Compressed HTML
The installation relies on the HTml-webpack-plugin, and compressed HTML is enabled by default in production. Automatically inserts built artifacts such as bundle.js, xx.css into the generated HTML. If there are multiple entries and only one HtmlWebpackPlugin is specified, all will be inserted into that HTML.
yarn add html-webpack-plugin -D
Copy the code
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, '.. / '.'public/index.html'),})]};Copy the code
Webpack advanced usage
Automatic cleanup of build artifacts
- Use NPM scripts to clean up the build directory:
rm -rf ./dist && webpack
- In Webpackage 5, the Output configuration provides the clean parameter, which is a Boolean type that, if true, clears the last build before it builds.
The PostCSS plugin autoprefixer is used to automatically complete the browser vendor prefix
Install dependencies:
yarn add postcss-loader autoprefixer -D
Copy the code
module.exports = {
module: {
rules: [{test: /\.less$/,
use: [
'style-loader'.'css-loader'.'less-loader',
{
loader: 'postcss-loader'.options: {
postcssOptions: {
plugins: [require('autoprefixer'],},},},],},},};Copy the code
Configure Autoprefixer in package.json:
{
"browserslist": ["1%" >."last 2 versions"."not ie <= 10"]}Copy the code
Px is automatically converted to REM
Px2rem-loader is used to automatically convert PX into REM, and with the Lib-Flexible library of Handamoy, font size of the root element can be calculated during rendering, so as to achieve self-adaptation on the mobile end.
Install dependencies:
yarn add px2rem-loader -D
yarn add lib-flexible -S
Copy the code
module.exports = {
module: {
rules: [{test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'.'less-loader',
{
loader: 'postcss-loader'.options: {
postcssOptions: {
plugins: [require('autoprefixer'],},},}, {loader: 'px2rem-loader'.options: {
remUnit: 75.remPrecision: 8,},},],},},};Copy the code
Since the current configuration does not support static resource inlining, the use of lib-flexible is covered in the next section.
Static resources are inlined
Meaning of resource inlining
Code level:
- Initialization script for the page frame
- Report related information (CSS and JS are loaded successfully)
- CSS inlining prevents page flickering and is better for the first screen load (come back with HTML)
Request level:
Reduce the number of HTTP network requests
- Small inline images or font (url – loader |
type: "asset"
)
Ejs is the default ejS template engine, because html-webpack-plugin is used.
Modify the WebPack configuration:
module.exports = {
module: {
rules: [{resourceQuery: /raw/,
type: 'asset/source',},],},};Copy the code
Inline resources to index.ejs:
<! DOCTYPEhtml>
<html lang="en">
<head><%= require('./meta.html? raw') %><title>How to play the webpack</title>
<script>< % =require('.. /node_modules/lib-flexible/flexible? raw') % ></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
Copy the code
Multipage application packaging common solution
Install dependencies:
yarn add glob -D
Copy the code
// Configure multi-page packing. The idea is to use glob to parse out the corresponding entry file, and then set the corresponding entry and HtmlWebpackPlugin
function setMpa() {
const entry = {};
const htmlWebpackPlugins = [];
const pagePaths = glob.sync(path.join(__dirname, '.. /src/mpa/**/index.js'));
pagePaths.forEach((pagePath) = > {
const name = pagePath.match(/src\/mpa\/(.*)\/index\.js/) [1];
entry[name] = pagePath;
htmlWebpackPlugins.push(
new HtmlWebpackPlugin({
filename: `${name}.html`.chunks: [name],
template: path.join(__dirname, '.. / '.`src/mpa/${name}/index.html`),}));return name;
});
}
Copy the code
Use the source map
Key words:
- Eval: Wrap module code in eval
- Source map: generate.map files (separate from source files)
- Cheap: Does not contain column information
- Inline: embed.map as DataURI instead of generating a separate.map file (which causes the source file to be too large)
- Module: Source map containing the Loader
Pay attention to the point
- For performance reasons, it is recommended not to use source Map in production for best packaging performance.
- The development environment is on, the online environment is off
- If you want to use it, you can use the Source Map type that does not analyze the business logic.
- You can upload the Source Map to the error monitoring system when troubleshooting problems online.
- Production environment:
devtool: source-map;
Have a high-quality Source map - Recommended development environment:
devtool: eval-cheap-module-source-map
Extract page common resources
Install the react/React-DOM base bundle using the CDN.
- Use htML-webpack-externals-plugin to separate the base library.
- Use the SplitChunkPlugin, which has been built in since WebPack4.
Separate react/react-dom base library
Install dependencies:
yarn add html-webpack-externals-plugin -D
Copy the code
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
module.exports = {
plugins: [
new HtmlWebpackExternalsPlugin({
externals: [{module: 'react'.entry: 'https://now8.gtimg.com/now/lib/16.8.6/react.min.js'.global: 'React'}, {module: 'react-dom'.entry: 'https://now8.gtimg.com/now/lib/16.8.6/react-dom.min.js'.global: 'ReactDOM',},],}),],};Copy the code
Entry uses the address of the CDN, and then adds the react/react-dom library to the index.ejs:
<! DOCTYPEhtml>
<html lang="en">
<head><%= require('./meta.html? raw') %><title>How to play the webpack</title>
<script>< % =require('.. /node_modules/lib-flexible/flexible? raw') % ></script>
</head>
<body>
<div id="root"></div>
<script src="https://now8.gtimg.com/now/lib/16.8.6/react.min.js"></script>
<script src="https://now8.gtimg.com/now/lib/16.8.6/react-dom.min.js"></script>
</body>
</html>
Copy the code
Chunk Parameter Description
- Async Decouple libraries introduced asynchronously (default)
- Initial separates libraries that are introduced synchronously
- All decouples all imported libraries (recommended)
Such as:
modulex.exports = {
optimization: {
splitChunks: {
chunk: 'async'.// Only asynchronously imported libraries are analyzed, and if they meet the set criteria, they are split into a separate package, i.e., subcontracting,}}};Copy the code
SplitChunksPlugin is used to separate the base package
Test: matches the packets to be separated. React and React-DOM are separated into vendors packages.
MinChunks: Sets the minimum number of references to 2.
MinSize: size of separated package volume.
module.exports = {
optimization: {
cacheGroups: {
minSize: 0.commons: {
test: /(react|react-dom)/,
name: 'vendors'.chunks: 'all'.minChunks: 2.// Remove libraries that have two or more page references}},}};Copy the code
Vendors Chunk is then introduced in HtmlWebpackPlugin:
modulex.exports = {
plugins: [
new HtmlWebpackPlugin({
chunks: ['vendors'].template: path.join(__dirname, '.. / '.'public/index.ejs'),})]};Copy the code
Tree Shaking
Tree shaker: Erase useless code
- The code must be es6
- Tree Shaking will fail if it has side effects
DCE (Elimination)
Mode: Production Tree shaking is enabled by default
- Code will not be executed, not reachable
- The results of code execution are not used
- Code only affects dead variables (write, not read)
if (false) {
/ / inaccessible
}
function getSex() {
return 'male';
}
getSex(); // The result of the code execution will not be used
var name = 'ywhoo'; // Write without reading
Copy the code
The principle of
Using the features of ES6 module:
- An import can only occur at the top level of a module
- The module name for import must be a string constant
- Import binding is immutable
During the compile phase (static analysis), the code used is identified, the unused code is marked, and the tagged code is removed during the Uglify phase.
Scope principle analysis
Symptom: Built code has a lot of closure code
Question:
- Bundle volume increases
- Function scope increases, memory overhead increases
How it works: Put all module code in a function scope in reference order, and then rename some variables appropriately to prevent variable name conflicts.
Implementation: Scope can reduce function declaration code and memory overhead
Use: mode to enable production by default
- Must be ES6 syntax.
Code splitting and dynamic import
Code splitting was introduced earlier, using splitChunks to separate the base package from the common functions.
Code splitting implications: For large Web applications, it’s not efficient to have all the code in one file, especially if certain blocks of code are used at specific times. One feature of WebPack is to break up your code base into chunks and load them as needed, rather than loading them all at once.
Scenarios used:
- Remove the same code block to a shared block
- Scripts are lazily loaded (loaded on demand), making the initial download smaller
Lazy loading of JS scripts:
- CommonJS: require.ensure
- ES6: Dynamic import (Babel conversion required)
How it works: Use jSONP at load time to create a script tag to dynamically import the script.
Implement an on-demand component import that asynchronously loads the code contained in getComponent when the button is clicked:
btn.addEventListener('click'.function () {
getComponent().then((comp) = > {
document.body.appendChild(comp);
});
});
async function getComponent() {
const { default: _} =await import('lodash');
const ele = document.createElement('div');
ele.innerHTML = _.join(
['hello'.'webpack'.'I'm dynamically importing generated code'].' '
);
return ele;
}
Copy the code
Problems encountered
Babelrc: async: async: async: async: async: async: async: async: async: async: async: async: async: async
{
"presets": [["@babel/preset-env",
{
"targets": {
"esmodules": true}}]]}Copy the code
Json is used in autoprefixer configuration of postcss. HMR cannot be used properly if this is configured:
{
"browserslist": ["1%" >."last 2 versions"."not ie <= 10"]}Copy the code
The workaround is to add the Target configuration to the WebPack configuration:
modulex.exports = {
target: 'web'};Copy the code
Use ESLint in WebPack
Good industry ESLint specification practices:
- Reality: eslint – config – reality, eslint – config – reality – base
- alloyteam: eslint-config-alloy
- ivweb: eslint-config-ivweb
- umijs: fabric
Specifies the esLint specification for the team:
- Don’t duplicate wheels, based on ESLint: Recommend configuration and improved
- Enable all rules that help you find code errors
- Helps keep the team’s code style uniform, not limit the development experience (ESLint usually checks for possible problems, while Prettier usually standardizes code style)
Eslint fall to the ground
- Integration with CI/CD systems
- Integration with WebPack (esLint fails without a build)
Practice for installing ESLint and Airbnb:
yarn add eslint eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y eslint-config-airbnb -D
Copy the code
Install eslint – loader:
yarn add eslint-loader babel-eslint -D
Copy the code
Solution 1: Webpack and CI/CD integration
Add Lint before build in the CI section and allow subsequent processes to be executed after Lint passes.
Added preCOMMIT hooks to the local development phase
Install the husky
yarn add husky -D
Copy the code
Added NPM script to incrementally check for modified files from Lint-staged:
{
"scripts": {
"precommit": "lint-staged"
},
"lint-staged": {
"*.{js}": ["eslint --fix"."git add"]}}Copy the code
To avoid bypassing git precommit hooks, you need to add a Lint step to the CI step.
Solution 2: WebPack integrates with ESLint
With eslint-Loader, the JS specification is checked at build time, suitable for new projects, and all JS files are checked by default.
module.exports = {
rules: [{test: /\.jsx? $/,
use: ['babel-loader'.'eslint-loader'],},]};Copy the code
module.exports = {
parser: 'babel-eslint'.extends: 'airbnb'.env: {
browser: true.node: true,},rules: {
'comma-dangle': 'off'.'no-console': 'off'.'jsx-quotes': 'off'.'jsx-a11y/click-events-have-key-events': 'off'.'jsx-a11y/no-static-element-interactions': 'off',}};Copy the code
Webpack packages components and base libraries
Rollup is better for packaging components and libraries; it’s much purer.
Webpack can be used to package applications as well as JS libraries.
Implement a large integer plus library packaging:
- You need to package the compressed and uncompressed versions
- Support AMD/CJS/ESM module introduction
Expose the library:
- Library: Specifies a global variable for the library
- LibraryTarget: Support library introduction method
Here is the implementation. I put the source code in the lib/big-number repository:
// Large integer addition
/** ** add from the ones bit@export
* @param {*} a string
* @param {*} b string
*/
export default function add(a, b) {
let i = a.length - 1;
let j = b.length - 1;
let res = ' ';
let carry = 0; / / carry
while (i >= 0 || j >= 0) {
let x = 0;
let y = 0;
let sum = 0;
if (i >= 0) {
x = +a[i];
i -= 1;
}
if (j >= 0) {
y = +b[j];
j -= 1;
}
sum = x + y + carry;
if (sum >= 10) {
carry = 1;
sum -= 10;
} else {
carry = 0;
}
res = sum + res;
}
if (carry) {
res = carry + res;
}
return res;
}
Copy the code
Webpack configuration:
const path = require('path');
const TerserWebpackPlugin = require('terser-webpack-plugin');
module.exports = {
mode: 'production'.entry: {
'big-number': path.join(__dirname, './src/index.js'),
'big-number.min': path.join(__dirname, './src/index.js'),},output: {
filename: '[name].js'.path: path.resolve(__dirname, './dist'),
library: 'bigNumber'.libraryTarget: 'umd'.clean: true,},optimization: {
// minimize: false,
// Webpack5 uses the terser-webpack-plugin to compress the code by default, which is used for customization here
minimizer: [
// Only files ending in.min.js are compressed
new TerserWebpackPlugin({
test: /\.min\.js$/i,}),],},};Copy the code
Once packaged, write an entry file for the component library, specified in package.json, such as main.js, where different versions are specified for different environment variables:
if (process.env.NODE_ENV === 'production') {
module.exports = require('./dist/big-number.min.js');
} else {
module.exports = require('./dist/big-number.js');
}
Copy the code
Now that the large integer add library is developed, publish it to the NPM repository, assuming you have logged in to the NPM account with NPM login, and then publish.
Note: If you use taobao image, you need to switch back to the official image.
It can be installed and used after a successful release:
import bigNumber from 'yw-big-number';
console.log(bigNumber('999'.'1')); / / 1000
Copy the code
Webpack implements SSR packaging
Why is server-side rendering needed and what are its advantages?
In the process of parsing HTML, if external JS and CSS are encountered, they need to be parsed later. Of course, browsers will pre-request resources, and non-critical resources will not block THE parsing of HTML. During the parsing process, the page will be blank. After the parsing is completed, the page starts to display. At this time, the LOADING may be the only way to display the real content after loading. If there are image resources, the image is still invisible and the page can be interactive only after loading is completed.
It can be found that the page has gone through a series of steps when loading before it is truly displayed in front of the user. The advantage of server rendering is that static resources and data are taken together with THE HTML, and the browser directly parses the HTML, and the JS script can be fully interactive after it is completed. It has the following advantages:
- Reduce white screen time
- All static resources, such as templates, are stored on the server
- Intranet machines pull data faster
- One HTML returns all the data
- Friendly to SEO
Bottom line: The core of server-side rendering is request reduction.
Code implementation ideas
- configuration
webpack.ssr.js
Export the client code to the UMD specification - The server code is implemented using Express, importing the exported client code, converting it into a string by ReactDOMServer’s renderToString method, putting it into the root node in the template, and then registering the route and enabling the listening port service.
The problem
- perform
node server/index.js
When submitted to theself is not defined
Since the server does not have the self global variable, add the following judgment at the top of the execution:
if (typeof self === 'undefined') {
global.self = {};
}
Copy the code
-
The packaged components need to be written in a compatible way. For example, the server module uses commonJS, and the client writing group needs to follow the CommonJS specification as well.
-
Change the FETCH or Ajax request method to isomorphic-fetch or AXIos.
-
Style issues (NodeJS can’t parse CSS)
- Server packaging ignores CSS parsing through ignore-loader
- Replace style-loader with isomorphic-style-loader (CSS module)
Using the packaged browser-side HTML as a template, set placeholders to dynamically insert components:
const template = fs.readFileSync(
path.join(__dirname, '.. /dist/index.html'),
'utf-8'
);
const useTemplate = (html) = > template.replace('<! --HTML_PLACEHOLDER-->', html);
app.get('/app'.(req, res) = > {
const html = useTemplate(renderToString(App));
res.status(200).send(html);
});
Copy the code
There will be a blank screen after implementation.
- How to process the first screen data?
After the server retrieves the data, the placeholder is replaced.
Optimize the display log of commands at build time
Use friendly-errors-webpack-plugin to provide friendly build information prompts.
Build exception and interrupt handling
Actively catch and handle build errors:
- Compiler triggers the done hook after each build
- Process. exit actively handles build errors
module.exports = {
plugins: [
function () {
// This points to compiler
this.hooks.done.tap('done'.(stats) = > {
if (
stats.compilation.errors &&
stats.compilation.errors.length &&
process.argv.indexOf('--watch') = = = -1
) {
process.exit(1); // Throw an exception and the terminal knows the build failed}}); },]};Copy the code
Write a maintainable WebPack build configuration
This section will write a maintainable WebPack build configuration library based on the previous configuration, which follows the writing specifications of the full library, including development specifications, smoke testing, unit testing, continuous integration, and so on, and will be published to the NPM community.
Build the significance of configuration abstraction into NPM packages
- generality
- Developers don’t have to worry about build configurations
- Unify team build scripts
- maintainability
- Build a properly configured split
- README documents and ChangeLog documents
- The quality of
- Smoke testing, unit testing, test coverage
- Continuous integration
Build configuration management options
- Manage the build of different environments through multiple configuration files, controlled by the webpack –config parameter
- Basic configuration webpack.common.js
- Development environment webpack.dev.js
- Production environment Webpack.prod.js
- SSR environment webpack. SSR. Js
- Design the build configuration as a repository for unified management
- Specifications: Git commit logging, README, Eslint specifications
- Quality: Smoke testing, unit testing, test coverage, and CI
Merge the configuration using webpack-merge.
Functional module design and directory structure
Developed using the ESLint specification
Since this is the base library development, only the eslint-config-Airbnb-base version of Airbnb is needed.
Install dependencies:
yarn add eslint babel-eslint eslint-config-airbnb-base -D
Copy the code
Configuration. The eslintrc. Js:
module.exports = {
parser: 'babel-eslint'.extends: 'airbnb-base'.env: {
browser: true.node: true,},rules: {
'comma-dangle': 'off'.'no-console': 'off'.'jsx-quotes': 'off'.'global-require': 'off'.'import/extensions': 'off'.'jsx-a11y/click-events-have-key-events': 'off'.'jsx-a11y/no-static-element-interactions': 'off'.'no-restricted-globals': 'off',}};Copy the code
Add esLint checks to scripts:
{
"scripts": {
"eslint": "eslint config --fix"}}Copy the code
Introduction and practical application of smoke testing
Smoke testing refers to the testing of the submitted software prior to detailed and in-depth testing. The purpose of this pre-testing is to expose serious problems such as the failure of basic functions of the software to be redistributed.
Smoke test execution:
- Determine whether the build was successful
- Whether the build artifacts have content
- Whether there are static resource files such as JS and CSS
- Whether there is an HTML file
Dependencies required by the installation:
yarn add rimraf webpack mocha assert glob-all
Copy the code
Write test cases that determine the success of the build:
const rimraf = require('rimraf');
const webpack = require('webpack');
const Mocha = require('mocha');
const path = require('path');
const mocha = new Mocha({
timeout: '10000'});// Delete the old build artifacts
// process.chdir(); // Change the working directory
rimraf('.. /.. /dist'.() = > {
const prodConfig = require('.. /.. /config/webpack.prod');
webpack(prodConfig, (err, stats) = > {
if (err) {
console.error(err);
process.exit(2);
}
console.log(
stats.toString({
colors: true.modules: false.children: false,}));console.log('webpack build succeeded, begin to test.');
mocha.addFile(path.join(__dirname, './html-test.js'));
mocha.addFile(path.join(__dirname, './js-css-test.js'));
mocha.run();
});
});
Copy the code
It is necessary to pay attention to whether the path is correct. HTML, JS and CSS test cases will not post codes. If you are interested, you can check them in my warehouse code.
Unit testing and test coverage
Simple test framework: Mocha/AVA, you need to install additional library assertion: chai/should js/expect/better – assert integration framework, out of the box: Jasmine/Jest (React)
Using Mocha + Chai, the main testing apis:
- Describe a file that needs to be tested
- It multiple test cases in a file
- Expect assertion
Execute the test command:
mocha add.test.js
Copy the code
Unit testing
Writing unit test cases:
Mocha looks for test/index.js by default.
describe('webpack config test.'.() = > {
require('./unit/webpack-base.test');
});
Copy the code
const assert = require('assert');
describe('webpack.common.js test case.'.() = > {
const baseConfig = require('.. /.. /config/webpack.common');
it('entry'.() = > {
// Test whether the file path of the entry is correct
assert.strictEqual(
baseConfig.entry.main,
'/Users/yewei/Project/source-code-realize/play-webpack/lib/yw-build-webpack/src/index.jsx'
);
});
});
Copy the code
The test passed as follows:
Test coverage
Installation of Istanbul.
After installation, modify the test:unit command:
{
"scripts": {
"test:unit": "istanbul cover mocha"}}Copy the code
Note: The test object code cannot have es6+ syntax code, otherwise the test coverage data will not be collected.
The result of executing YARN test:unit is as follows, and the coverage directory is generated in the root directory to store the code coverage results:
Continuous integration and Travis CI
Role of Continuous integration:
- Find errors quickly
- Prevent branches from straying too far from the trunk
The idea: Code must pass automated testing before it can be integrated into the trunk. If you have one error, you can’t integrate.
Github’s most popular CI’s:
Access to the Travis CI:
- Travis click login
- Activate projects that require continuous integration
- Add.travis. Yml to the project root directory
Create a new project on Github, and then upload the code under yw-build-Webpack to the repository by performing the following steps:
#Go to yw-build-webpack and initialize Git
git init
git add .
git commit -m "xxx"
#Add the remote repository
git remote add origin https://github.com/weiTimes/yw-build-webpack.git
#Push the code
git push -u origin master
Copy the code
Add. Travis. Yml:
language: node_js # language
sudo: false
node_js:
- 12.161.
cache: # save cache
- npm
- yarn
before_install: # install dependencies
- npm install -g yarn
- yarn
scripts: # Execute test
- yarn test
Copy the code
When code is priced up, a walk triggers a build task.
Publish build packages to the NPM community
Release NPM
Add a user: NPM adduser
Upgraded version
- Upgrade patch version: NPM Version Patch
- Upgrade minor version: NPM Version Minor
- Major version: NPM Version Major
Release: NPM publish
Go to the root directory of the project you want to publish, then log in to NPM and publish:
npm login
npm publish
Copy the code
To release a patch, perform the following steps:
git add .
git commit -m "doc: udpate reamde"
npm version patch
git push -u origin master
npm publish
Copy the code
Git commit specification and Changelog generation
Advantages of a good Git commit specification:
- Accelerate the process of code review
- Generate changelog based on git commit metadata
- Convenient for subsequent maintenance
Angular Git Commit specification:
Added preCOMMIT hooks to the local development phase
Add dependencies:
yarn add conventional-changelog-cli @commitlint/{config-conventional,cli}
Copy the code
Refer to Git submission specifications
Changlog generated
After committing according to the specification, it is easy to generate Changelog
Semantic version
Open source project version information amway
- It usually consists of three members: X.Y.Z
- The version is strictly incremented: 16.2.0 -> 16.3.0 -> 16.3.1
- For major releases, alpha (internal), beta (external small range), RC (public beta) and other prior releases 16.2.0-RC.123 can be released
Follow the Semver specification:
- Avoid circular dependencies
- Reducing dependency conflicts
Specification format:
- Major version number: Incompatible API changes were made
- Minor version: Added the backward compatibility function
- Revision number: Backward-compatible issue fixes
conclusion
Here’s what you should have learned by now:
- Flexible configuration of WebPack according to project requirements.
- Understand the principles of Tree Shaking, Scope, etc.
- It can package multi-page application, component library and base library, SSR, etc.
- Write a maintainable Webpack configuration, control code quality with unit tests, smoke tests, and so on, and use Travis for continuous integration.
The second part will cover webPack performance optimization, packaging principles, and writing loaders and plugins.