三分钟让你学会搭建生产webpack

163 阅读3分钟

前端进阶 - webpack

前言

  • 已经对 Vue / React / Angular 等框架基本用法熟练掌握了?
  • 想进阶 webpack ,却面对官网晦涩冗长的文字难以下手?
  • 每天高强度使用的 npm run dev , 却不知道其背后的含义?
  • 面试遇到 webpack 总是面露难色?

我将会用最简洁的字符, 帮助你进阶webpack

笔者意在抛砖引玉,更多api及其细致特性请移步webpack官网翻阅。


阅读本文之前,你需要

  • 掌握 JS CSS HTML
  • 掌握ES6常用语法
  • 完成 webpack 官网 起步demo

本地环境

node v14+

webpack v5+


为什么要构建和打包(本文用webpack)?

代码层面:

  • 打包出来的代码,体积更小( 通过 webpack 过滤无用代码,压缩,合并等操作 )
  • 支持高级语言语法,提升开发效率( 通过 webpackTS, ES6+, scss等转译成绝大部分浏览器支持的语言 )
  • 编译前或编译时,提示代码错误eslint等帮你校验代码 )

业务层面:

  • 统一开发环境( 分成 dev [ test, gray, ... ] prod 环境去开发,高效 )
  • 统一产出标准( 所有人产出的代码均转译成高兼容性的标准,稳定 )
  • 统一构建规范( 把代码构建到指定目录,便捷 )

基本配置

拆分配置

使用: webpack-merge

作用: 通过抽离不同环境下的共同配置,来消除重复代码,提高开发效率

开发环境走 common & dev 的配置

生产环境走 common & prod 的配置

示例源码

webpack-demo
├─ config
│  ├─ webpack.common.js	// 存放公共的配置
│  ├─ webpack.dev.js	// 存放开发环境的配置
│  ├─ webpack.prod.js	// 存放生成环境的配置
├─ dist
├─ src
│  ├─ index.js 	// 入口文件
├─ package.json 

config/webpack.common.js

const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')

module.exports = {
    entry: path.join(__dirname, '..', 'src', 'index'),
    plugins: [new HtmlWebpackPlugin()],
};

config/webpack.dev.js

const webpackCommonConf = require('./webpack.common.js')
const { merge } = require('webpack-merge')

module.exports = merge(
    webpackCommonConf, // 融进 common 配置
    {
        mode: 'development',
        devServer: {
            port: 10086,
        }
    }
)

config/webpack.prod.js

const webpackCommonConf = require('./webpack.common.js')
const { merge } = require('webpack-merge')
const path = require('path')

module.exports = merge(
    webpackCommonConf,  // 融进 common 配置
    {
        mode: 'production',
        output: {
            filename: 'main.js',
            path: path.resolve(__dirname, '..', 'dist'),
        },
    }
)

src/index.js

function component() {
    const element = document.createElement('div');
    element.innerHTML = 'hello webapck'
    return element;
}

document.body.appendChild(component());

package.json

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack --config config/webpack.prod.js",
    "dev": "webpack serve --config config/webpack.dev.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "html-webpack-plugin": "^5.5.0",
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.11.1",
    "webpack-merge": "^5.8.0"
  }
}

babel配置

使用: babel-loader

webpack 官网声明:“兄弟们,babel-loader 与我无关,它由社区维护,安全性我不保证嗷。”

作用: ECMAScript 2015+ => 老版浏览器能看懂的js代码

比如用例中的箭头函数,在babel-loader的转义前后的区别

src/index.js

/**
 * Babel is a toolchain that 
 * is mainly used to convert ECMAScript 2015+ code 
 * into a backwards compatible version of JavaScript 
 * in current and older browsers or environments. 
 * 
 * ECMAScript 2015+ => 老版浏览器能看懂的js代码
 */

/**
 * Babel Input: ES2015 arrow function
 * Babel 转义 箭头函数
 */
[1, 2, 3].map(n => n + 1);
  • 使用 babel-loader

dist/main.js

[1,2,3].map((function(n){return n+1}));
  • 不使用 babel-loader

dist/main.js

[1,2,3].map((a=>a+1));

示例源码

webpack-demo
├─ config-no-babel
│  ├─ webpack.common.js	// 没用 babel-loader
│  ├─ webpack.dev.js		
│  ├─ webpack.prod.js		
├─ config
│  ├─ webpack.common.js // 用了 babel-loader
│  ├─ webpack.dev.js		
│  ├─ webpack.prod.js		
├─ dist
├─ src
│  ├─ index.js
├─ package.json 

config/webpack.common.js

const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')

module.exports = {
    entry: path.join(__dirname, '..', 'src', 'index'),
    module: {
        rules: [
            {
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env'],

                        /**
                         * You can also speed up babel-loader by 
                         * as much as 2x by using the cacheDirectory option. 
                         * This will cache transformations to the filesystem. 
                         * 
                         * 官方建议打开,可以缓存转义文件,提高2倍编译速度
                         */
                        cacheDirectory: true
                    }
                },
    
                /* 转义 src 目录 */
                include: path.join(__dirname, '..', 'src', 'index'),
                
                /* 不转义 node_modules 目录 */
                exclude: /node_modules/,
            }
        ],
    },
    plugins: [new HtmlWebpackPlugin()],
};

package.json

{
  ...
  "scripts": {
    "build": "webpack --config config/webpack.prod.js",
    "build:with-no-babel": "webpack --config config-no-babel/webpack.prod.js",
    "dev": "webpack serve --config config/webpack.dev.js"
  },
  ...
  "devDependencies": {
    ...
    "@babel/core": "^7.19.3",
    "@babel/preset-env": "^7.19.4",
    "babel-loader": "^8.2.5"
  }
}

可以使用 npm run build 来打包使用 babe-loader 的配置

可以使用 npm run build:with-no-babel 来打包不使用 babel-loader 的配置

抽离 & 压缩 css文件

使用: MiniCssExtractPlugin

作用: 分离压缩,按需加载。

This plugin extracts CSS into separate files. It creates a CSS file per JS file which contains CSS. It supports On-Demand-Loading of CSS and SourceMaps.

示例代码供比较,分离压缩前后,打包文件的区别

  • 使用 MiniCssExtractPlugin
├─ dist
│  ├─ index.html
│  ├─ main.js
│  ├─ main.css // 直接插到 html 文件中
  • 不使用 MiniCssExtractPlugin
├─ dist
│  ├─ index.html
│  ├─ main.js // css文件通过 style-loader 转义,插入到 js 文件中

示例源码

webpack-demo
├─ config-without-miniCssExtractPlugin
│  ├─ webpack.common.js
│  ├─ webpack.dev.js		
│  ├─ webpack.prod.js	// 没用 MiniCssExtractPlugin
├─ config
│  ├─ webpack.common.js 
│  ├─ webpack.dev.js		
│  ├─ webpack.prod.js	// 用了 MiniCssExtractPlugin
├─ dist
├─ src
│  ├─ style.css
│  ├─ index.js
├─ package.json 

config/webpack.prod.js

const webpackCommonConf = require('./webpack.common.js')
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { merge } = require('webpack-merge')
const path = require('path')

module.exports = merge(
    webpackCommonConf,
    {
        mode: 'production',
        output: {
            filename: 'main.js',
            path: path.resolve(__dirname, '..', 'dist'),
        },
        plugins: [
            new MiniCssExtractPlugin()
        ],
        module: {
            rules: [
                {
                    test: /\.css$/i,
                    use: [MiniCssExtractPlugin.loader, "css-loader"],
                },
            ],
        }
    }
)

config-without-miniCssExtractPlugin/webpack.prod.js

const webpackCommonConf = require('./webpack.common.js')
const { merge } = require('webpack-merge')
const path = require('path')

module.exports = merge(
    webpackCommonConf,
    {
        mode: 'production',
        output: {
            filename: 'main.js',
            path: path.resolve(__dirname, '..', 'dist'),
        },
        module: {
            rules: [
                {
                    test: /\.css$/i,
                    use: ["style-loader", "css-loader"],
                },
            ],
        }
    }
)

src/style.css

body{
    background: #ccc;
}

src/index.js

import "./style.css";

package.json

{
  ...
  "scripts": {
    "build": "webpack --config config/webpack.prod.js",
    "build:with-no-css-mini": "webpack --config config-without-miniCssExtractPlugin/webpack.prod.js",
    "dev": "webpack serve --config config/webpack.dev.js"
  },
  ...
  "devDependencies": {
    ...
    "style-loader": "^3.3.1",
    "css-loader": "^6.7.1",
    "mini-css-extract-plugin": "^2.6.1",
  }
}

可以使用 npm run build 来打包使用 mini-css-extract-plugin 的配置

可以使用 npm run build:with-no-css-mini 来打包不使用 mini-css-extract-plugin 的配置

未完待续...