webpack 工程环境搭建

环境搭建基本步骤

新建项目,比如 webpack-study

判断单个前端,还是团队

处理安装依赖

首先优化安装依赖镜像源,根目录新建文件 .npmrc (可选)

registry=https://registry.npm.taobao.org

安装 webpack 基本环境

npm init -y
npm install webpack webpack-cli --save-dev

参考:https://webpack.docschina.org/guides/getting-started

在根目录新建 webpack.config.js 和入口文件 src/index.js,目录结构

webpack-study
|- /dist
  |- main.js
  |- index.html
|- /node_modules  
|- /src
  |- index.js
|- .npmrc
|- package.json
|- package-lock.json
|- webpack.config.js

选择合适的样式 静态资源(图片第三方字体) es6+(vue react ts)

比如样式:借助 css-loader 处理 css 的语法,借助 style-loader 使用css

如果使用 less, sass 之类的,需要安装对应的 less-loader, sass-loader, 如

npm install less less-loader --save-dev

然后在 webpack.config.js 中添加

module.exports = {
  module: {
    rules: [
      {
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      }
    ],
  },
}

这里需要注意,因为一个 loader 只做一件事情,less-loader 处理 .less 需要依赖 less 插件所以需要同时安装 lessless-loader, 参考:https://webpack.docschina.org/loaders/less-loader

postcss 处理编译 css

一般常用 autoprefixer 自动添加 css 浏览器前缀和 cssnano 压缩 css 代码, 需要安装 postcss-loader,postcss,autoprefixer,cssnano

npm install --save-dev postcss-loader postcss autoprefixer cssnano

新建 postcss.config.js 文件配置

module.exports = {
  plugins: [
    require('autoprefixer'),
    require('cssnano')
  ]
}

参考:https://github.com/postcss/autoprefixer#webpack

然后在 webpack.config.js 中添加

module.exports = {
  module: {
    rules: [
      {
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader']
      }
    ],
  },
}

此时基本配置已完成,但是 为了兼容到目标浏览器,需要配置下 browserslist

第一种写法,在 package.json 中添加

{
  "browserslist":["last 2 versions","> 1%"]
}

另外一种,直接新建 .browserslistrc 使用换行作为条件组合

last 2 versions
> 1%

第三种写法(不推荐),直接在 postcss.config.js 以参数传入

module.exports = {
  plugins: [
    require('autoprefixer')({
      overrideBrowserslist: ['last 2 versions', '> 1%']
    })
  ]
}

last 2 versions 表示浏览器最近两个大版本,比如可以写成 last 2 chrome version, 最新 chrome 浏览器版本 96.0.4664.93(正式版本),那么久可以兼容到 96版和95版
> 1% 表示浏览器份额大于 1% 的浏览器
完整参考:https://github.com/browserslist/browserslist#full-list
browserslist的数据来源 http://browserl.ist/

执行 npx browserslist 可以查看支持的浏览器版本

参考:https://github.com/browserslist/browserslist#debug

mini-css-extract-plugin 提取 css 到单独的文件

安装

npm install --save-dev mini-css-extract-plugin

注意 moduleplugins 都要配置

const miniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  module: {
    rules: [     
      {
        test: /\.less$/,
        use: [miniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader']
      }
    ]
  },
  plugins: [    
    new miniCssExtractPlugin({
      filename: '[name].css'
    })
  ]
}

如何编写一个自定义 loader

新增 loader 文件 myLoaders/replace-loader.js

// loader 是一个函数
//! 不可以是箭头函数
// 必须有返回值,返回一个 str buffer
// 支持配置 this.query
// 如何返回多个信息 this.callback
// 如何处理异步 this.async
// 如何处理多个 loader
module.exports = function (source) {
  console.log(this.query)
  return source.replace(/hello/g, 'hi')
}

和 loader 文件 myLoaders/replace-async-loader.js

// loader 是一个函数
//! 不可以是箭头函数
// 必须有返回值,返回一个 str buffer
// 支持配置 this.query
// 如何返回多个信息 this.callback
// 如何处理异步 this.async
// 如何处理多个 loader
module.exports = function (source) {
  console.log(this.query)
  // const msg = source.replace(/index/g, this.query.info)
  // return this.callback(null, msg)

  const callback = this.async()
  setTimeout(() => {
    const msg = source.replace(/index/g, this.query.info)
    return callback(null, msg)
  },2000)
}

即可在 webpack.config.js 中使用了

module.exports = {  
  resolveLoader: {
    modules: ['node_modules', './myLoaders'] // 在配置的目录下寻找 module
  },  
  module: {
    rules: [      
      {
        test: /\.js$/,
        use: [
          'replace-loader',
          {
            loader: 'replace-async-loader',
            options: {
              info: 'lzw'
            }
          }
        ]
      }
    ]
  },
}

Loader 最佳实践

实现 style-loader, css-loader, less-loader 的功能,对应的文件

myLoaders/k-less-loader 文件

// 把 less 语法 编译成 css
const less = require('less')
module.exports = function (source) {
  less.render(source, (error, output) => {
    this.callback(error, output.css)
  })
}

myLoaders/k-css-loader 文件

// 把 css 转 字符串
module.exports = function (source) {
  return JSON.stringify(source)
}

myLoaders/k-style-loader 文件

// 动态创建 style 
// 把 source 塞进 style
// 把 style 放进文件 head 中
module.exports = function (source) {
  return `
    const tag = document.createElement('style')
    tag.innerHTML = ${source}
    document.head.appendChild(tag)
  `
}

这样就可以在 webpack.config.js 中使用了

module.exports = {  
  resolveLoader: {
    modules: ['node_modules', './myLoaders'] // 在配置的目录下寻找 module
  },  
  module: {
    rules: [      
      {
        test: /\.less$/,
        use: ['k-style-loader', 'k-css-loader', 'k-less-loader']
      },
    ]
  },
}

图片处理

webpack 4.x 使用 file-loader, url-loader 处理图片,安装

url-loader 包含了file-loader,所以可以只使用 url-loader

npm i -D file-loader url-loader

然后在 webpack.config.js 中配置

module.exports = {     
  module: {
    rules: [      
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name].[ext]',
            public: '../images', // css 中添加资源的目录
            outputPath: 'images', // 输出目录
            limit: 8192, // 字节,小于 8K 的图片转 base64
          }
        },// 还是使用 webpack 4.x 的方法
        // type: 'asset/inline' // webpack 5.0 的用法 相当于 url-loader
      },
    ]
  },
}

webpack 5.x 改用资源模块(asset module),直接 webpack.config.js 中配置

module.exports = {  
  output: {    
    assetModuleFilename: 'images/[hash][ext][query]' //  webpack 5.x 图片等资源输出目录
  },   
  module: {
    rules: [      
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: 'asset/inline' // webpack 5.0 的用法 相当于 url-loader
      },
    ]
  },
}

参考: https://webpack.docschina.org/guides/asset-modules/

第三方字体处理

webpack.config.js 中配置

module.exports = { 
  module: {
    rules: [      
      {
        test: /\.(woff|woff2|svg|eot)$/,
        // 使用 webpack 4.x 的方法
        // use: {
        //   loader: 'file-loader',
        //   options: {
        //     name: '[name].[ext]',
        //     public: '../fonts', // css 中添加资源的目录
        //     outputPath: 'fonts', // 输出目录
        //   }
        // },
        // webpack 5.0 的用法 相当于 file-loader
        type: 'asset/resource',
        generator: {
          filename: 'fonts/[hash][ext][query]'
        }
      },
    ]
  },
}

配置后,即可在 css 中使用

@font-face {
  font-family: "webfont";
  font-display: swap;
  src: url("../fonts/webfont.woff") format("woff");
}
body {
  font-family: "webfont";
}

webpack 文件指纹策略

module.exports = {
  output: {  
    // 输出的文件名称, 多出口 name 对应 entry 里的: index/a
    filename: '[name]-[hash:4]-[chunkhash:4]-[contenthash:4].js', 
  },
}

[hash] 是以项目为单位,项目内容改变,则生成 hans 改变 [chunkhash] 以 chunk 为单位,任意一个文件内容改变,则整个 chunk 组的模块 hash 都改变 [contenthash] 以自身内容为单位 一般来说,css 文件使用 contenthash, js 文件使用 chunkhash

使用 clean-webpack-plugin 清空构建目录

安装

npm i -D clean-webpack-plugin

webpack.config.js 中配置后,执行构建时会自动清空 dist 目录老的文件

const {CleanWebpackPlugin} = require('clean-webpack-plugin')

module.exports = {
   plugins: [
    new CleanWebpackPlugin(),
   ]
}

多页面打包通用方案

首先需要规划好目录,比如 list, index 页面目录

webpack-study 
|- /src
  |- /index
    |- index.html
    |- index.js
  |- /list
    |- index.html
    |- index.js

然后需要安装下 glob 文件匹配工具

npm i -D glob

新增 webpack.mpa.config.js, 可以添加命令行 "mpa": "webpack --config ./webpack.mpa.config.js"package.json

const path = require('path')
const htmlWebpackPlugin = require('html-webpack-plugin')
const miniCssExtractPlugin = require('mini-css-extract-plugin')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const glob = require('glob')

const setMPA = () => {
  const entry = {}
  const htmlWebpackPlugins = []

  const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'))


  entryFiles.map((item, index) => {
    const entryFile = entryFiles[index]
    const match = entryFile.match(/src\/(.*)\/index\.js$/)
    const pageName = match[1]

    entry[pageName] = entryFile
    htmlWebpackPlugins.push(new htmlWebpackPlugin({
      template: path.join(__dirname, `./src/${pageName}/index.html`),
      filename: `${pageName}/index.html`,
      chunks: `[${pageName}]` // 对应 entry 里的: index/list
    }))
  })

  return {
    entry, htmlWebpackPlugins
  }
}

const {entry, htmlWebpackPlugins} = setMPA()
module.exports = {
  entry,
  output: {
    path: path.resolve(__dirname, './mpa'), // 输出的文件目录,必须是绝对路径
    filename: 'js/[name]-[chunkhash:8].js', // 输出的文件名称, 多出口 name 对应 entry 里的: index/list
    assetModuleFilename: 'images/[hash][ext][query]' // webpack 5.x 图片等资源输出目录
  },
  mode: 'development', // none development production
  // loader
  module: {
    rules: [
      //...
      {
        test: /\.css$/,
        // 多个 loader 情况下,执行顺序是自后往前的
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.less$/,
        use: [miniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader']
      },
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: 'asset/resource', // webpack 5.0 的用法 相当于 url-loader
      },
      {
        test: /\.(woff|woff2|svg|eot)$/,
        // webpack 5.0 的用法 相当于 file-loader
        type: 'asset/resource',
        generator: {
          filename: 'fonts/[hash][ext][query]'
        }
      },
    ]
  },

  // plugin
  plugins: [
    ...htmlWebpackPlugins,
    new CleanWebpackPlugin(),
    new miniCssExtractPlugin({
      filename: 'css/[name]-[contenthash:4].css'
    })
  ]
}

整体上就是通过 setMPA 函数动态生成 entryhtmlWebpackPlugins 以达到多页面无需手动配置