Webpack

Webpack4.0

  • webpack4将 webpack 和 webpack-cli 分开了,需要安装cli后才能使用命令行工具
    yarn add webpack webpack-cli --dev
  • webpack4.0以后支持零配置打包,直接执行 yarn webpack ,默认将 src/index.js 作为入口文件,打包到 dist/main.js 中
  • 工作模式
    • yarn webpack --mode development 开发模式,优化打包速度,不压缩,方便调试
    • yarn webpack --mode production 生产模式,优化打包结果,压缩代码
    • yarn webpack --mode none 原始模式,不对代码做任何处理

Webpack配置文件

/webpack.config.js,导出一个配置对象

const path = require('path')
module.exports = {
    // 打包模式
    mode: 'development',
    // 入口文件,./ 不能省略
    entry: './src/main.js',
    // 输出目录
    output: {
        //输出文件的默认值是 ./dist/main.js
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist'),
        // bundle.js中的 __webpack_require__.p
        // 使用html-webpack-plugin插件自动生成html的话无需指定
        publicPath: 'dist/'
    },
    // 源代码地图
    devtool: 'source-map',
    // webpack-dev-server配置
    devServer: {
        // hot: true,
        hotOnly: true
        proxy: {}
    },
    module: {
        //loader
        rules: []
    },
    // 插件
    plugins: []
}

打包结果运行原理

bundle.js

(function(modules){
    function require(moduleId){
        
        return require(0)
    }
})([
    (function(module, exports, require){
    
    }),
    (function(module, exports, require){})
])
  1. 使用立即执行函数封装代码,避免全局污染
  2. 缓存每个模块的输出值并自执行函数
  3. 处理模块间的相互引用

资源模块加载 Loader

  1. webpack 默认只能处理 js 文件,如果要处理非JS类型的文件,需要手动安装一些第三方加载器。
  • Html加载器:html-loader
  • css加载器:css-loader style-loader
  • 文件资源加载器:file-loader url-loader
    • 对小文件通过url-loader转换为 Data Url(base64的字符串)嵌入代码中
    • 对大文件使用file-loader按照传统方式加载
  • ES6语法编译:babel-loader @babel/core @babel/preset-env
  1. 配置 webpack.config.js 的 module 节点,这个对象上有一个 rules 属性,这个 rules 数组中存放了所有第三方文件的匹配和处理规则。
module.exports = {
    
    module: {
        rules: [
            {
                test: /.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }
            },
            {
                test: /.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /.png$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        limit: 10*1024  //10KB
                    }
                }
            },
            {
                test: /.html$/,
                use: {
                    loader: 'html-loader',
                    options: {
                        attrs: ['img:src', 'a:href']
                    }
                }
            }
        ]
    }
}
  • use 表示用哪些模块来处理 test 所匹配到的文件,use 中相关loader模块的调用顺序是从后向前
  • 通过options对loader添加配置选项

Webpack模块加载方式

  • 遵循ESM标准的import声明
  • 遵循CommonJS标准的require函数
  • 遵循AMD标准的define函数和require函数
  • 样式代码中的@import指令和url函数
  • Html代码中图片标签的src属性

建议项目中统一使用同一种标准加载模块。

常用加载器分类

  • 编译转换类 如:css-loader
  • 文件操作类 如:file-loader
  • 代码检查类 如:eslint-loader

Loader的工作原理

将加载的资源文件转换成标准JavaScript代码

loader工作原理


Webpack插件机制 Plugin

增强webpack自动化能力

  1. 安装插件 yarn add xxx --dev
  2. 在 webpack.config.js 中导入
  3. 配置 plugins 属性(值是一个数组)
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')

module.exports = {
    plugins: [
        new CleanWebpackPlugin(),
        // 可以传入一个配置对象,对生成的html进行配置
        new HtmlWebpackPlugin({
            title: 'Html Webpack Plugin',
            meta: {
                viewport: 'width=device-width'
            },
            // 指定一个模版文件,按照模版生成html
            template: './src/index.html'
        }),
        // 同时输出多个页面文件,需要创建多个实例
        new HtmlWebpackPlugin({
            filename: 'about.html',  //默认是index.html
        }),
        new CopyWebpackPlugin([
            //需要复制的文件路径
            'public'
        ])
    ]
}

常用插件

  • clean-webpack-plugin 自动清除输出目录文件
  • html-webpack-plugin 生成自动使用(所有)打包结果的Html
  • copy-webpack-plugin 复制无需构建的静态文件
    (文件数量过多时开发过程中不使用此插件,只有上线前打包时才会使用)

插件机制的工作原理

  • 通过在webpack生命周期的钩子(Compiler Hooks)中挂载任务函数来实现。

  • 一个插件就是一个函数或者一个包含apply函数的对象,一般定义为一个类型,使用时在plugins中构建一个实例来使用。

Demo: 开发一个插件,用来删除bundle.js中的注释

class MyPlugin {
    // webpack启动时会自动执行apply方法
    apply(compiler){
        console.log('MyPlugin 启动')
        //emit钩子:webpack往输出目录输出文件之前执行
        compiler.hooks.emit.tap('Myplugin', compilation => {
            // compilation 可以理解为此次打包的上下文
            for (const name in compilation.assets) {
                //资源文件名称
                console.log(name)
                //获取资源文件内容
                console.log(compilation.assets[name].source())
                if(name.endsWith('.js')){
                    //获取带有注释的js文件
                    const content = compilation.assets[name].source()
                    //去掉文件中的注释
                    const withoutComments = content.replace('/\/\*\*+\*\//g', '')
                    //覆盖当前资源文件
                    compilation.assets[name] = {
                        source: () => withoutComments,
                        size: () => withoutComments.length
                    }
                }
            }
        })
    }
}

增强webpack开发体验

  1. 通过 HTTP Server 运行
  2. 自动编译+自动刷新浏览器
  3. 提供 Source Map 支持

Webpack Dev Server

  1. 安装 yarn add webpack-dev-server --dev
  2. 在配置文件中配置webpack-dev-server
module.exports = {
    devServer: {
        //指定静态资源目录,值可以是一个字符串或一个数组
        conentBase: './public',
        //配置代理
        proxy: {
            '/api': {
                //目标服务器
                target: 'https://api.github.com',
                //重写代理路径
                pathRewrite: {
                    '^/api': ''
                },
                //不能使用localhost:8080 作为请求的主机名
                changeOrigin: true
            }
        }
    }
}
  1. 运行 yarn webpack-dev-server 或将启动命令写入Npm Scripts中
    • –open 自动打开浏览器
    • –hot 模块热更新
  • 打包的文件临时存放在内存中,而不是物理磁盘
  • 在开发过程中,通过 devServer 的 contentBase 属性,可以指定静态资源目录

Source Map

解决源代码和打包后的运行代码不一致所带来的调试问题

  • source map文件映射了转换过后的代码与源代码之间的关系;
  • 转换后的文件需要引入source map文件才能使用
    //# sourceMappingURL = xxx.map
  • webpack 配置 source map 的不同模式,每种方式的效率和效果各不相同:
    • devtool: ‘source-map’,生成source map文件,能定位错误代码的行和列
    • devtool: ‘eval’,使用eval执行模块代码,不生成source map文件,只能定位源文件名称;
    • eval-source-map:使用eval执行模块代码,生成source map文件,能定位错误代码的行和列;
    • cheap-eval-source-map:阉割版,只定位到行;
    • cheap-module-eval-source-map 模式:得到loader处理之前的(自己手写的)源代码
    • inline-source-map:以dataurl的方式将source map嵌入到代码中
    • hidden-source-map:生成source map文件,但不引入
    • nosources-source-map:能定位错位代码位置,但在开发工具中看不到源代码

选择建议:
开发过程用 cheap-module-eval-source-map 模式;
生产环境用 none,或者用 nosources-source-map 模式。

Webpack HMR

  • Hot Module Replacement,模块热替换/更新。

页面不刷新的情况下,及时更新模块代码,解决浏览器自动刷新带来的页面状态和用户输入丢失的问题。

  • webpack-dev-server中集成了HMR,通过 --hot参数 或 配置文件 开启。
const webpack = require('webpack')

module.exports = {
    
    devServer: {
        //hot: true
        hotOnly: true
    },
    
    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ]
}
  • 开启HMR后,css文件可以实现不刷新浏览器自动更新,其他模块需要手动处理模块更新后的逻辑。

在入口函数(如main.js)中通过 HMR APIs 处理引入的模块的HMR

// 注册模块更新后的处理函数 两个参数:1.模块路径 2.处理函数
module.hot.accept('./editor', () => {
    console.log('模块已更新')
    ...
})

HMR使用注意事项

  • 处理HMR的代码报错,页面还是会自动刷新—使用hotOnly
  • 没有开启HMR,HMR API报错—先判断module.hot存在再使用
if(module.hot){
    module.hot.accept()
}
  • 与业务无关的处理HMR的代码,不会影响代码的正常运行

Webpack生产环境优化

开发环境更注重开发效率;
生产环境更注重运行效率;
不同的工作环境需要创建不同的webpack配置。

不同环境下的配置

在配置文件中添加判断条件,根据环境名参数(env)判断不同的工作环境,从而导出不同配置。适合中小型项目

const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
// 导出一个函数
module.exports = (env, argv) => {
    //开发环境
    const config = {
        mode: 'development',
        entry: './src/main.js',
        output: {
          filename: 'js/bundle.js'
        },
        devtool: 'cheap-eval-module-source-map',
        devServer: {
          hot: true,
          contentBase: 'public'
        },
        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'
          }),
          new webpack.HotModuleReplacementPlugin()
        ]
    }
    
    //生产环境
    if(env === 'production'){
        //改变工作模式
        config.mode = 'production'
        //禁用source map
        config.devtool = false
        //增加生产模式需要使用的插件
        config.plugins = [
            ...config.plugins,
            new CleanWebpackPlugin(),
            new CopyWebpackPlugin(['public'])
        ]
    }
    
    return config
}
  • 运行 yarn webpack,以开发模式进行打包
  • 运行 yarn webpack --env production,以生产模式进行打包

多配置文件

一个环境对应一个配置文件。适合大型项目

  • 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 Test',
            template: './src/index.html'
        })
    ]
}
  • webpack.dev.js
const webpack = require('webpack')
const merge = require('webpack-merge')
const common = require('./webpack.commom.js')

module.exports = merge(common, {
    mode: 'development',
    devtool: 'cheap-eval-module-source-map',
    devServer: {
        hot: true,
        contentBase: 'public'
    },
    plugins: [
        new webpack.hot.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.commom.js')

module.exports = merge(common, {
    mode: 'production',
    plugins: [
        new CleanWebpackPlugin()
        new CopyWebpackPlugin(['public'])
    ]
})
  1. 安装webpack-merge模块,yarn add webpack-merge --dev
  2. 在不同环境的配置文件下合并对应的webpack配置
  3. 运行打包命令时要指定 config 参数,
    yarn webpack --config webpack.dev.js
    或者将命令定义在Npm Scripts中,然后运行 yarn build

package.json

{
    "scripts": {
        "dev": "webpack --config webpack.dev.js",
        "build": "webpack --config webpack.prod.js"
    }
}

production模式下的优化功能

DefinePlugin

通过 process.env.NODE_ENV 为代码注入全局变量,
变量的值要求为JS代码片段

const webpack = require('webpack')
module.exports = {
    entry: './src/main.js',
    output: {
        filename: 'bundle.js'
    },
    plugins: [
        new webpack.DefinePlugin({
            API_BASE_URL: JSON.stringfy('http://api.example.com')
        })
    ]
}

在main.js中就使用变量API_BASE_URL

console.log(API_BASE_URL)

Tree Shaking

去掉代码中未引用的部分(dead-code)

  • 在使用生产模式打包时Tree Shaking会自动开启
  • 使用其他模式打包时也可以在 optimization 中手动开启
  • Tree Shaking的前提是代码使用ESM规范
module.exports = {
    mode: 'none',
    entry: './src/index.html',
    output: {
        filename: 'bundle.js'
    },
    optimization: {
        //只导出被使用的成员
        usedExports: true,
        //压缩代码
        minimize: true,
        //合并模块
        concatenateModules: true,
        //开启副作用功能
        sideEffects: true
    }
}

合并模块

用法:配置 optimization 的 concatenateModules 属性

作用:尽可能地将所有模块合并输出到一个函数中,提升运行效率,减小代码体积。

sideEffects

webpack4 新特性,一般用于npm包标记是否有副作用

副作用:模块执行时除了导出成员外所做的事情。
如果没有副作用,并且未被引用,打包时就可以被shaking掉。

  • sideEffects在生产模式下也会自动开启
  • 通过 optimization 的 sideEffects 开启,在 package.json 中的 sideEffects 字段标记
{
    "sideEffects": [
        "*.css",
    ]
}

代码分割/分包

避免bundle.js体积过大,将打包结果按照一定的规则分离到多个bundle中,根据运行需要按需加载

多入口打包

适用于多页面应用,一个页面对应一个打包入口。

module.exports = {
    //多入口打包时入口配置为一个对象
    entry: {
        index: '.src/index/html',
        about: '.src/about.html'
    },
    //动态输出文件名
    output: '[name].bundle.js',
    optmization: {
        splitChunks: {
            //提取所有公共模块到单独的bundle
            chunks: 'all'
        }
    },
    module: {
        
    },
    plugins: [
        //html-webpack-plugin默认使用所有打包结果
        //使用chunks属性为每个Html指定所使用的打包结果
        new HtmlWebpackPlugin({
            title: 'Index',
            template: './src/index.html',
            filename: 'index.html',
            chunks: ['index']
        }),
        new HtmlWebpackPlugin({
            title: 'About',
            template: './src/about.html',
            filename: 'about.html',
            chunks: ['about']
        })
    ]
}

提取公共模块:配置优化属性 optimization 将页面公共的部分提取出来,如公共的样式和请求api的部分。

动态导入(ESM)

动态导入的模块会被自动分包,实现按需加载。

  • import方法返回一个promise对象,通过.then得到导入的模块对象
  • 动态导入的模块打包后默认以序号命名(如:1.bundle.js)
  • 可以通过行内注释为打包后的文件指定名称(打包后的文件名为:name.bundle.js)
import(/* webpackChunkName: name */'模块路径').then(module => {
    。。。
})

MiniCssExtractPlugin

将css从打包结果中单独提取到css文件中,从而实现css的按需加载。
建议:超过150k的样式文件才需要单独提取

  1. 安装 yarn mini-css-extract-plugin
  2. 在配置文件中导入
  3. 在 plugins 中添加
  4. 修改样式加载器 style-loader
    style-loader 是将样式通过style标签注入,使用MiniCssExtractPlugin的话需要使用 MiniCssExtractPlugin.loader 将样式以link的方式引入

OptimizeCssAssetsWebpackPlugin

webpack内部的压缩插件只针对js,其他资源文件需要额外的插件来压缩

  1. 安装 optimize-css-assets-webpack-plugin
  2. 导入
  3. 添加到plugins数组中
    • 一般将这个插件添加到 optimization 的 minimizer 数组中,这样的话只有当 minimize 开启或者用生产模式打包时,该插件才会工作
    • 配置了自定义压缩器 minimizer 后,内置的压缩器 terser-webpack-plugin 会被覆盖掉,需要手动添加回来以后js文件才会被压缩
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: './src/index.js',
    output: {
        filename: '[name].bundle.js'
    },
    optimization: {
        minimizer: [
            new TerserWebpackPlugin(),
            new OptimizeCssAssetsWebpackPlugin()
        ]
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    // 'style.loader',
                    MiniCssExtractPlugin.loader,
                    'css-loader'
                ]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin()
    ]
}

输出文件名Hash

部署前端资源文件时一般会启用服务器的静态资源缓存,在设置filename时给文件名加上hash,就可以将缓存失效时间设置得很长

module.exports = {
    entry: '.src/index.html',
    output: {
        filename: '[name]-[contenthash:8]'
    }
}
  • 项目级别的hash,任何地方发生改动,hash值都会发生变化
  • chunkhash,同一路的文件发生改变,这一路文件(js和css)的hash值都会改变
  • contenthash,文件级别,每个文件有不同的hash值
  • :数字 可以指定hash值的位数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值