Skip to content
On this page

webpack基础

模块化

概述

传统开发问题

  • 命名冲突
  • 文件依赖

模块化就是把单独的一个功能封装到一个模块(文件中),模块之间相互隔离,但是可以通过特定的接口公开内部成员,也可以依赖别的模块

模块化开发的好处:

  • 方便代码的重用
  • 提升开发效率
  • 方便后期维护

模块化规范

  • AMD(过时)

  • CMD(过时)

  • CommonJS(node端)

    • 模块分为单文件模块
    • 导出:module.exportsexports
    • 导入:require('模块')
  • ES6模块化

    • 每个 js 文件都是一个独立的模块
    • 导入:import
    • 导出:export

ES6 模块化的基本语法

  • 默认导出 与 默认导入

    js
    // m1.js
    const a = 1
    export default { a }
    
    // index.js
    import m1 from './m1.js'
    console.log(m1) // { a: 1 }

    每个模块中,只允许使用唯一的一次 export default,否则会报错,如果文件没有导出任何变量,默认引入的是个空对象 {}

  • 按需导出 与 按需导入

    js
    // m1.js
    export const b = 2
    
    // index.js
    import m1, { b } from './m1.js'
    console.log(m1) // { a: 1 }
    console.log(b) // 2

    每个模块中可以多次按需导出

  • 直接导入并执行模块代码

    js
    // m2.js
    console.log('直接执行')
    // index.js
    import './m2.js'

webpack 基本使用

为什么需要构建工具

  • ES6语法
  • 转换 JSX
  • CSS 前缀补全/预处理器
  • 压缩混淆
  • 图片压缩

核心

  • mode 指定构建环境 developmentproductionnone

  • entry 单入口 或 多入口

  • output 单出口 或 多出口

  • loaders 加载器

  • plugins 插件

js
npm i webpack webpack-cli -D
// webpack.config.js
const path = require('path')
module.exports = {
    // 编译模式
    mode: 'development', // production
    // 打包入口 str/object { app: './src/index.js', main: './src/main.js' }
    entry: path.join(__dirname, './src/index.js'),
    // 打包出口 str/object
    output: {
        path: path.join(__dirname, './dist') // 打包文件的存放路径
        filename: '[name].js' // 打包文件的名称 [name] 对应多入口占位符
    }
}
// 执行打包命令webpack
webpack

热更新

js
npm i webpack-dev-server -D
// webpack.config.js
module.exports = {
    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ],
    devServer: {
        contentBase: './dist',
        hot: true
    }
}

html-webpack-plugin 生成预览页面

js
npm i html-webpack-plugin -D
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
    ...
    // plugins 数组是 webpack 打包期间会用到的一些插件列表
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html' // 指定要用的模板文件
            filename: 'index.html' // 指定生成的文件名称
        })
    ]
}

loader 加载器

webpack 默认只能打包处理 js 文件,其他非 js 模块,需要loader 加载器才能正常打包,否则报错

常用的 less-loader sass-loader url-loader(打包css中与url路径相关的文件)babel-loader

js
// webpack.config.js
module.exports = {
    module: {
        rules: [
            { 
                test: /\.css$/, // 表示匹配的文件类型 
                use: [
                    'style-loader',
                    'css-loader'
                ] // 表示要调用的loader,loader顺序是固定的,从后往前
            },
            {
                test: /\.jpg|png|gif|ttf|eot|svg|woff$/,
                use: 'url-loader?limit=16940' // 可以数组和字符串 ? 接参数
            },
            // 或者
            {
                test: /\.jpg|png|gif|ttf|eot|svg|woff$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 10240
                        }
                    }
                ]
            }
        ]
    }
}
  • postcss-loader 自动添加 css 的兼容前缀

    js
    npm i postcss-loader autoprefixer -D
    // postcss.config.js
    module.exports = {
        plugins: [
            require('autoprefixer')
        ]
    }
    // webpack.config.js
    module.exports = {
        ...
        module: {
            rules: [
                { 
                	test: /\.css$/,
                    use: [
                        'style-loader',
                        'css-loader',
                        'postcss-loader'
                    ]
                }
            ]
        }
    }
  • css 单位 px 自动转换 rem

    • 使用 px2rem-loader
    • 使用手淘 lib-flexible
    js
    npm i px2rem-loader -D
    npm i lib-flexible -S
    // webpack.config.js
    module.exports = {
        module: {
            rules: [
                ...
                {
                    test: /\.less$/,
                    use: [
                        'style-loader',
                        'css-loader',
                        'less-loader',
                        {
                            loader: 'px2rem-loader',
                            options: {
                                remUnit: 75, // 相对单位 1rem = 75px
                                remPrecision: 8 // 转换后小数点位数
                            }
                        }
                    ]
                }
            ]
        }
    }
  • babel 处理 js 高级语法

    js
    // babel转换器
    npm i babel-loader @babel/core @babel/runtime -D
    // babel语法插件
    npm i @babel/preset-env @babel/plugin-transform-runtime @babel/plugin-proposal-class-properties -D
    // babel.config.js
    module.exports = {
        presets: ['@babel/preset-env'],
        plugins: [
            '@babel/plugin-transform-runtime',
            '@babel/plugin-proposal-class-properties'
        ]
    }
    // webpack.config.js
    module.exports = {
        module: {
            rules: [
                { 
                	test: /\.js$/,
                    use: 'babel-loader',
                    exclude: /node_modules/ // 排除 node_modules 中的 js 文件处理
                }
            ]
        }
    }
  • vue 组件加载器

    js
    npm i vue-loader vue-template-compiler -D
    // webpack.config.js
    const VueLoaderPlugin = require('vue-loader/lib/plugin')
    module.exports = {
        module: {
            rules: [
                ...
                { test: /\.vue$/, use: 'vue-loader' }
            ]
        },
        plugins: [
            new VueLoaderPlugin()
        ]
    }

文件指纹设置

解决版本和缓存

  • hash
  • chunkhash
  • contenthash
js
// webpack.config.js
const MinCssExtractPlugin = require('min-css-extract-plugin')
module.exports = {
    ...
    output: {
        ...
        filename: '[name]_[chunkhash:8].js'
    },
    module: {
        rules: [
            {
                test: /\.jpg|png|gif|ttf|eot|svg|woff$/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            name: '[name]_[hash:8][ext]'
                            limit: 10240
                        }
                    }
                ]
            },
            { 
            	test: /\.css$/,
                use: [
                     MinCssExtractPlugin.loader, // 提取css
                    'css-loader',
                    'postcss-loader'
                ]
            }
        ]
    },
    plugins: [
        new MinCssExtractPlugin({
            filename: '[name]_[contenthash:8].css'
        })
    ]
}

clean-webpack-plugin 删除目录

js
npm i clean-webpack-plugin -D
// webpack.config.js
const CleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = {
    ...
	plugins: [
        new CleanWebpackPlugin() // 默认删除ouput指定的构建输出目录
    ]
}

raw-loader 静态文件内联

js
npm i raw-loader@0.5.1 -D
// 内联 html
${require('raw-loader!babel-loader!./meta.html')}

// 内联 js
<script>${require('raw-loader!babel-loader!../test.js')}</script>

html-webpack-externals--plugin 基础库分离

js
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin')

module.exports = {
    plugins: [
        ...
        new HtmlWebpackExternalsPlugin({
            externals: [
                {
                    module: 'react',
                    entry: 'https://11.url.cn/now/lib/16.2.0/react.min.js', // 能是本地文件
                    global: 'React',
                },
                {
                    module: 'react-dom',
                    entry: 'https://11.url.cn/now/lib/16.2.0/react-dom.min.js',
                    global: 'ReactDOM',
                },
            ]
        })
    ]
}

image-webpack-loader 图片压缩

js
npm i image-webpack-loader -D
// webpack.config.js
module.exports = {
    modules: {
        rules: [
            ...
            {
                test: /.(png|jpg|gif|jpeg)$/,
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            name: '[name]_[hash:8].[ext]'
                        }
                    },
                    {
                        loader: 'image-webpack-loader',
                        options: {
                          mozjpeg: {
                            progressive: true,
                            quality: 65
                          },
                          // optipng.enabled: false will disable optipng
                          optipng: {
                            enabled: false,
                          },
                          pngquant: {
                            quality: '65-90',
                            speed: 4
                          },
                          gifsicle: {
                            interlaced: false,
                          },
                          // the webp option will enable WEBP
                          webp: {
                            quality: 75
                          }
                        }
                    }
                ]
            }
        ]
    }
}

purgecss-webpack-plugin 剔除无用 css

js
const PurgecssPlugin = require('purgecss-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const PATHS = {
    src: path.join(__dirname, 'src')
};
module.exports = {
    plugins: [
        ...
        new MiniCssExtractPlugin({
            filename: '[name]_[contenthash:8].css'
        }),
        new PurgecssPlugin({
            paths: glob.sync(`${PATHS.src}/**/*`,  { nodir: true })
        })
    ]
}

一些概念

tree shaking (摇树优化)

1个模块可能有多个方法,只要其中的某个方法使用到了,则整个文件都会被打到 bundle 里面去,tree shaking 就是只把用到的方法打入 bundle,没用到的方法会在 uglify 阶段被擦除掉

js
// .babelrc
{
    modules: false // 开启
}

// production mode 环境默认开启

scope hoisting

将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突

通过 scope hoisting 可以减少函数声明代码和内存开销

代码分割

将代码库分割成 chunks (语块),当代码运行到需要它们的时候再进行加载

  • 抽离相同代码到一个共享块

  • 脚本懒加载,使得初始下载的代码更小

    脚本懒加载 js 方式

    • CommonJS:require.ensure
    • ES6:动态 import (目前还没有原生支持,需要 babel 转换)
    js
    npm i @babel/plugin-syntax-dynamic-import -S
    // .babelrc
    {
        "plugins": ["@babel/plugin-syntax-dynamic-import"]
    }
    // index.js
    import('./test.js').then(Test => {
        // Test.default 组件
    })