webpack的配置与使用

文章内容输出来源:拉勾教育前端高薪训练营

打包工具解决的是前端整体的模块化,并不单指javascript模块化

模块打包工具的由来

  • ES Modules存在环境兼容问题
  • 模块文件过多,网络请求频繁
  • 所有的前端资源都需要模块化

模块打包工具的目的

  • 将开发阶段的es6+等新特性在生产阶段编译成es5
  • 将开发阶段多个分散的多个模块文件在生产阶段打包成一个bundle.js
  • 支持不同类型的资源模块打包

webpack使用

准备阶段

安装webpack依赖

$ yarn add webpack webpack-cli --dev
基础使用
// 不设置mode时默认为production模式
$ yarn webpack
// development模式
$ yarn webpack --mode development
// none模式
$ yarn webpack --mode none

或者在package.json中定义任务

"scripts": {
  "build": "webpack"
}
$ yarn build

在使用webpack打包后的文件时,可以去掉script标签上的type="module“属性

loader加载器使用
  • webpack内部默认只会处理js文件,其他类型文件需要使用loader加载器来解
  • loader是webpack的核心,通过loader可以加载任何类型的文件
  • webpack默认只会处理es6+的import和export,其他特性需要babel-loader来解析
$ yarn add babel-loader @babel/core @babel/preset-env --dev
webpack模块加载方式
  • 遵循ES Modules标准的import声明
  • 遵循CommonJS标准的require函数
  • 遵循AMD标准的define函数和require函数
  • 样式代码中的@import指令和url函数
  • HTML代码中图片标签的src属性
开发一个loader
  • loader工作就像是一个管道,可以执行多个loader
  • 因为webpack解析资源打包后直接将代码拼接在了打包的js中,所以最终必须返回javascript代码,否则语法可能出错

md-loader.js,解析markdown文件

const marked = require('marked')

// 将.md直接解析成html并返回
module.exports = source => {
  const html = marked(source)
  // return `module.exports = ${JSON.stringify(html)}`
  return `export default ${JSON.stringify(html)}`
}

// 或直接返回html,然后交给html-loader来处理
module.exports = source => {
  const html = marked(source)
  return html
}
插件机制

相比于loader,plugin拥有更宽的能力范围

常用插件
  • clean-webpack-plugin 自动清除打包文件夹
  • html-webpack-plugin 自动生成html,指定多个new HtmlWebpackPlugin()可生成多个html
  • copy-webpack-plugin 拷贝文件到指定目录
  • mini-css-extract-plugin 提取css到单个文件,使用时处理css不需要用style-loader,建议css文件大小超过150kb时使用此插件提取css
module.exports = {
  module: {
    rules: [
      {
	    test: /.css$/,
	    use: [
	      // 'style-loader', // 使用mini-css-extract-plugin时,不需要style-loader来解析
	      MiniCssExtractPlugin.loader,
	      'css-loader',
	    ],
	  },
    ]
  }
}
  • optimize-css-assets-webpack-plugin 压缩样式文件
  • terser-webpack-plugin 压缩js文件
// 可以配置在plugins中,但是在plugins时随时都会开启压缩,所以一般压缩类插件建议配置在minimizer中,配置了minimizer后,js压缩也需要在minimizer中手动配置
module.exports = {
  optimization:{
    minimizer: [
      new OptimizeCssAssetsWebpackPlugin(),
      new TerserWebpackPlugin(),
    ],
  },
}
自定义插件
  • 自定义插件必须是一个函数或者是一个包含apply方法的对象
  • 通过在生命周期的钩子中挂载函数实现扩展

clearCommentsWebpackPlugin.js打包后清除bundle.js中的注释

class ClearCommentsWebpackPlugin {
  constructor(options = {}) {
    this.options = options
  }

  apply (compiler) {
    console.log('clear start')
    compiler.hooks.emit.tap('ClearCommentsWebpackPlugin', compilation => {
      for (const name in compilation.assets) {
        if (name.endsWith('.js')) {
          const contents = compilation.assets[name].source()
          const clearComments = contents.replace(/\/\*\*+\*\//g, '')
          compilation.assets[name] = {
            source: () => clearComments, // 插件必须写的参数
            size: () => clearComments.length, // 插件必须写的参数
          }
        }
      }
    })
  }
}

module.exports = ClearCommentsWebpackPlugin
自动编译
watch工作模式

此模式需要手动刷新浏览器

$ yarn webpack --watch
browserSync自动刷新浏览器
$ browser-sync dist --files "**/*"
webpack-dev-server
  • 提供了用于开发的http server,集成了“自动编译”和“自动刷新浏览器”等功能
  • 默认只会serve打包输出文件,只要是webpack输出的文件都可以直接被访问到,静态资源需要配置devServer下的contentBase属性
    webpack.config.js
devServer: {
  contentBase: './public',
}
  • 配置代理服务,解决跨域问题
    webpack.config.js
proxy: {
  '/api': {
    // http://localhost:8080/api/user => https://api.github.com/api/users
    target: 'https://api.github.com',
    // http://localhost:8080/api/user => https://api.github.com/users
    pathRewrite: {
      '^/api': '',
    },
    // 不能使用loaclhost:8080作为请求主机名,为true时会以实际代理服务器作为主机名请求
    changeOrigin: true,
  },
}
# webpack4
$ yarn webpack-dev-server
# webpack5
$ yarn webpack serve --open
source map源代码地图

使用方式,js文件最后添加source map注释

//# sourceMappingURL=xxxxxxxx.min.map
webpack配置source map
//# sourceMappingURL=xxxxxxxx.min.map

webpack.config.js

devtool: 'source-map',
source map各配置对比
devtoolbuildrebuildproductionqualty
(none)fastestfastestyesbundled code
evalfastestfastestnogenerated code
cheap-eval-source-mapfastfasternotransformed code(lines only)
cheap-module-eval-source-mapslowfasternooriginal source(lines only)
eval-source-mapslowestfastnooriginal source
cheap-source-mapfastslowyestransformed code(lines only)
cheap-module-source-mapslowsloweryesoriginal source(lines only)
inline-cheap-source-mapfastslownotransformed code(lines only)
inline-cheap-module-source-mapslowslowernooriginal source(lines only)
source-mapslowestslowestyesoriginal source
inline-source-mapslowestslowestnooriginal source
hidden-source-mapslowestslowestyesoriginal source
nosources-source-mapslowestslowestyeswithout source content
  • eval模式:将代码打包放到一个eval函数中执行,在eval函数的最后通过url方式说明对应的文件路径,此模式不会生成source map文件
  • eval-source-map模式:相比于eval,生成了source map,可以定位到行和列
  • cheap-eval-source-map模式:阉割版的eval-source-map,而且源代码经过了loader加工,只能定位到行
  • cheap-module-eval-source-map模式:阉割版的eval-source-map,只能定位到行,但是源代码没有被loader加工,为手写的源代码

source map各名词解析

  • eval-是否使用eval执行模块代码
  • cheap-source map是否包含行信息
  • module-是否能够得到loader处理之前的源代码
  • inline-将source map以data-url方式嵌入到代码
  • hidden-生成了source map文件,但是开发者工具中看不到,通常用于开发第三方包
  • nosources-能看到行列信息,但是开发者工具中看不到源代码
选择合适的source map
  • 开发环境中建议选择:cheap-module-eval-source-map
  • 生产环境中建议选择:none
HMR(Hot Module Replacement)模块热替换
$ yarn webpack-dev-server --hot

或者

webpack.config.js

const webpack = require('webpack')
devServer: {
  // hot: true,
  hotOnly: true, // 结局hot模式HMR时代码报错自动刷新页面
},
plugins: [
  new webpack.HotModuleReplacementPlugin(),
],

此时只实现了样式文件热替换,js文件和媒体资源是无规律的,需要手动处理模块热替换逻辑

index.js

import createHeading from './heading.js'
let lastHeading = createHeading

// 手动热替换js
module.hot.accept('./heading', () => {
  // 此处编写热替换代码逻辑
  document.body.removeChild(createHeading)
  const newCreateHeading = createHeading()
  document.body.appendChild(newCreateHeading)
  lastHeading = newCreateHeading
})

// 手动替换图片
module.hot.accept('./src/assets/image/icon_order_pay.png', () => {
  // 此处编写热替换代码逻辑
  img.src = icon1
})

注意事项:处理HMR的代码报错会导致自动刷新(使用hotOnly替换hot配置来解决)

根据环境配置不同的代码

配置方式

  • 配置文件根据环境不同导出不同配置
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 = {
    // config配置
  }
  
  if (env === 'production') {
    config.mode = 'production'
    config.devtool = false
    config.plugins = [
      ...config.plugins,
      new CleanWebpackPlugin(),
      new CopyWebpackPlugin({
        patterns: ['public'],
      }),
    ]
  }

  return config
}
$ yarn webpack --env production
  • 一个环境对应一个配置文件,公共配置放在webpack.common.js,生产配置放在webpack.prod.js,开发配置放在webpack.dev.js,通过webpack-merge来合并common和对应环境
module.exports = merge(common, {
  mode: 'production',
  plugins: [
    new CleanWebpackPlugin(),
    new CopyWebpackPlugin({
      patterns: ['public'],
    }),
  ],
})
$ yarn webpack --config webpack.prod.js
DefinePlugin

为代码注入全局成员,production模式默认启用,并注入process.env.NODE_ENV,可用于根据不同的运行环境匹配不同的参数,例如可以设置api请求host

module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      API_BASE_URL: '"https://api.example.com"', // 注入的字符串要加引号,因为打包后代码会直接引用,也可以JSON.stringify('https://api.example.com')
    }),
  ],
}
Tree Shaking

production模式自带Tree Shaking

module.exports = {
  optimization: {
    usedExports: true, // 负责标记“枯树叶”
    minimize: true, // 负责“摇掉”他们
    concatenateModules: true, // 尽可能将所有模块合并输出到一个函数中,既提升来运行效率,又减少了代码的体积
  },
}
Tree Shaking与babel-loader

Tree Shaking 的前提是ES Modules输出的代码,老版本的babel中默认会将ES Modules转换成CommonJS,新版本默认不会将ESM转换成CommonJS

强制转换ESM=>CommonJS

{
  test: /.js$/,
  use: {
    loader: 'babel-loader',
    options: {
      presets: [
        ['@babel/preset-env', { modules: 'commonjs' }], // modules: false 不转换
      ],
    },
  }, // es6+解析
},
sideEffects 副作用

副作用:模块执行时除了导出成员之外所作的事情,一般用于npm包标记是否有副作用,production模式下会自动开启

module.exports = {
  optimization: {
    sideEffects: true, // 副作用是否开启
  },
}
// package.json
{
  "sideEffects": false,// 标识代码是否有副作用
}

// or用数组标识出有副作用的文件
{
  "sideEffects": [
    "./src/index.js",
    "*/main.css"
  ] // 标识代码是否有副作用
}
代码分包

所有的代码都被打包到一起会造成bundle体积过大,且并不是一开始就需要加载所有的模块

分包的方式:

  • 多入口打包,适用于多页面应用,一个页面对应一个打包入口,公共部分单独提取
module.exports = {
  entry: {
    index: './src/index.js',
    heading: './src/heading.js',
  },
  output: {
    filename: '[name].bundle.js',
  },
  optimization: {
    splitChunks: {
      chunks: 'all', // 提取所有公共模块
    },
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack sample',
      template: './index.html',
      filename: 'index.html',
      chunks: ['index'],
    }), 
    new HtmlWebpackPlugin({
      title: 'webpack heanding',
      template: './heanding.html',
      filename: 'heanding.html',
      chunks: ['heanding'],
    }), 
  ]
}
  • 按需动态导入,需要用到某个模块时,再加载这个模块,动态导入的模块会被自动分包
// 使用时动态import组件
if (true) {
  import('./src/heading.js').then(({ default: heading }) => {
    // 处理逻辑
  })
}

// 魔法注释,会使得打包的文件使用注释的名称作为打包文件的名字,如果多个模块都使用相同的魔法注释,则这几个模块都会被打包进同一个文件中
if (true) {
  import(/* webpackChunkName: 'heanding' */'./src/heading.js').then(({ default: heading }) => {
    // 处理逻辑
  })
}

vue、react等单页应用,路由组件动态导入会自动分包

  • 输出文件名Hash,在开启静态资源缓存时,及时替换客户端缓存文件,通过配置filename来实现,一般使用contenthash:8
/** 
 * hash:项目级别,项目中任一地方改都改,所有打包的名称都会改变
 * chunkhash:同一chunk的文件改变,会重新打包生成新文件
 * contenthash:文件级别,对应文件改变时,只会重新打包改变此文件
 * :8代表hash长度
 */
output: {
  filename: '[name]-[contenthash:8].bundle.js',
}
配置代码总结

webpack.config.js

const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const ClearCommentsWebpackPlugin = require('./src/assets/js/clearCommentsWebpackPlugin')

module.exports = {
  mode: 'none', // 打包模式development、production、none
  entry: './src/index.js', // webpack打包入口文件,相对路径时'./'不能省略
  output: { // webpack打包输出配置,为一个对象
    filename: 'bundle.js', // 输出文件名称
    path: path.join(__dirname, 'dist'), // 输出文件目录,为一个绝对路径
    // publicPath: 'dist/', // 设置静态资源目录,/不能省略
  },
  module: { // 配置资源模块加载器
    rules: [
      {
        test: /.md$/,
        use: [
          'html-loader',
          './src/assets/js/md-loader',
        ]
      }, // 自定义解析markdown的loader
      {
        test: /.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        }, // es6+解析
      },
      // {
      //   test: /.html$/,
      //   use: {
      //     loader: 'html-loader',
      //     options: {
      //       // attrs: ['img:src', 'a:href'], // webpack4用法:默认只解析img:src,其他属性需要配置
      //       sources: { // webpack5用法
      //         list: [
      //           {
      //             tag: 'img',
      //             attribute: 'src',
      //             type: 'src',
      //           },
      //           {
      //             tag: 'a',
      //             attribute: 'href',
      //             type: 'src',
      //           },
      //         ],
      //       },
      //     },
      //   }, // html解析
      // },
      {
        test: /.css$/,
        use: [
          'style-loader',
          'css-loader',
        ], // css解析=>style解析,loader由后向前的顺序解析
      },
      // {
      //   test: /.png$/,
      //   use: 'file-loader', // 文件解析,直接拷贝物理文件
      // },
      {
        test: /.png$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 10 * 1024, // 限制文件为10KB以下的转换为data-url,超过10KB由file-loader处理
          },
        } // 文件解析,转换成data-url编码
      },
    ]
  },
  plugins: [
    new CleanWebpackPlugin(), // 自动清除打包文件夹
    new HtmlWebpackPlugin({
      title: 'webpack sample',
      template: './index.html',
    }), // 自动生成html
    // new CopyWebpackPlugin([
    //   'public',
    // ]), // 拷贝文件,webpack4用法
    new CopyWebpackPlugin({
      patterns: [
        'public',
      ],
    }), // 拷贝文件,webpack5用法
    new ClearCommentsWebpackPlugin(),
  ],
}
$ yarn build
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值