zoukankan      html  css  js  c++  java
  • Webpack入门

    前言

    webpack 作为前端最知名的打包工具,能够把散落的模块打包成一个完整的应用,大多数的知名框架 cli 都是基于 webpack 来编写。这些 cli 为使用者预设好各种处理配置,使用多了就会觉得理所当然,也就不在意是内部是如何配置。如果脱离 cli 开发,可能就无从下手了。

    最近在开发一些单页项目时,出于需求便开始从头搭建项目配置,本文主要分享搭建时用到的配置。

    准备工作

    快速生成 package.json:

    npm init -y
    

    必不可少的 webpack 和 webpack-cli:

    npm i webpack webpack-cli -D
    

    入口、出口

    webpack 的配置会统一放到配置文件中去管理,在根目录下新建一个名为 webpack.config.js 的文件:

    const path = require('path')
    
    module.exports = {
      entry: {
        main: './src/js/main.js'
      },
      output: {
        // filename 定义打包的文件名称
        // [name] 对应entry配置中的入口文件名称(如上面的main)
        // [hash] 根据文件内容生成的一段随机字符串
        filename: '[name].[hash].js',
        // path 定义整个打包文件夹的路径,文件夹名为 dist
        path: path.join(__dirname, 'dist')
      }
    }
    

    entry 配置入口,可配置多个入口。webpack 会从入口文件开始寻找相关依赖,进行解析和打包。

    output 配置出口,多入口对应多出口,即入口配置多少个文件,打包出来也是对应的文件。

    修改 package.json 的 script 配置:

    "scripts": {
      "build": "webpack --config webpack.config.js"
    }
    

    这样一个最简单的配置就完成了,通过命令行输入 npm run build 就可以实现打包。

    配置项智能提示

    webpack 的配置项比较繁杂,对于不熟悉的同学来说,如果在输入配置项能够提供智能提示,那开发的效率和准确性会大大提高。

    默认 VSCode 并不知道 webpack 配置对象的类型,通过 import 的方式导入 webpack 模块中的 Configuration 类型后,在书写配置项就会有智能提示了。

    /** @type {import('webpack').Configuration} */
    module.exports = {
    
    }
    

    环境变量

    一般开发中会分开发和生产两种环境,而 webpack 的一些配置也会随环境的不同而变化。因此环境变量是很重要的一项功能,使用 cross-env 模块可以为配置文件注入环境变量。

    安装模块 cross-env

    npm i cross-env -D
    

    修改 package.json 的命令传入变量:

    "scripts": {
      "build": "cross-env NODE_ENV=production webpack --config webpack.config.js"
    }
    

    在配置文件里,就可以这样使用传入的变量:

    module.exports = {
      devtool: process.env.NODE_ENV === 'production' ? 'none' : 'source-map'
    }
    

    同理,在项目的 js 内也可以使用该变量。

    设置 source-map

    该选项能设置不同类型的 source-map 。代码经过压缩后,一旦报错不能准确定位到具体位置,而 source-map 就像一个地图, 能够对应源代码的位置。这个选项能够帮助开发者增强调试过程,准确定位错误。

    为了体验它的作用,我在源代码中故意输出一个不存在的变量,模拟线上错误:

    在预览时,触发错误:

    很明显错误的行数是不对应的,下面设置 devtool 让 webpack 在打包后输出 source-map 文件,用于定位错误。

    module.exports = {
      devtool: 'source-map'
    }
    

    再次触发错误,source-map 文件起作用准确定位到代码错误的行数。

    source-map 一般只在开发环境用于调试,上线时绝对不能带有 source-map 文件,这样会暴露源代码。下面通过环境变量来正确设置 devtool 选项。

    module.exports = {
      devtool: process.env.NODE_ENV === 'development' ? 'source-map' : 'none'
    }
    

    devtool 的可选项很多,它的品质和生成速度有关联,比如只定位到某个文件,或者定位到某行某列,相应的生成速度会快或更慢。可以根据你的需求进行选择,更多可选值请查看 webpack 文档

    loader 与 plugin

    loader 与 plugin 是 webpack 的灵魂。如果把 webpack 比作成一个食品加工厂,那么 loader 就像很多条流水线,对食品原料进行加工处理。plugins 则是在原有的功能上,添加其他功能。

    loader 基本用法

    loader 配置在 module.rules 属性上。

    module.exports = {
      module: {
        rules: []
      }
    }
    

    下面来简单了解下 loader 的规则。一般常用的就是 testuse 两个属性:

    1. test 属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。
    2. use 属性,表示进行转换时,应该使用哪个 loader。
    rules: [
      {
        test: /.css$/,
        use: ['css-loader']
      }
    ]
    // 也可以写为
    rules: [
      {
        test: /.css$/,
        loader: 'css-loader'
      }
    ]
    

    上面例子是匹配以 .css 结尾的文件,使用 css-loader 解析。

    当有些 loader 可传入配置时,可以改为对象的形式:

    rules: [
      {
        test: /.css$/,
        user: [
          {
            loader: 'css-loader',
            options: {}
          }
        ]
      }
    ]
    

    最后需要记住多个 loader 的执行是严格区分先后顺序的,从右到左,从下到上。

    plugin 基本用法

    plugin 配置在 plugins 属性上。

    module.exports = {
      plugins: []
    }
    

    一般 plugin 会暴露一个构造函数,通过 new 的方式使用。plugin 的参数则在调用函数时传入。plugin 命名一般是将按照原名转为大驼峰。

    const { CleanWebpackPlugin } = require('clean-webpack-plugin')
    
    module.exports = {
      plugin: [
        new CleanWebpackPlugin()
      ]
    }
    

    生成 html 文件

    没有经过任何配置的 webpack 打包出来只有 js 文件,使用插件 html-webpack-plugin 可以自定义一个 html 文件作为模版,最终 html 会被打包到 dist 中,而 js 也会被引入其中。

    安装 html-webpack-plugin:

    npm i html-webpack-plugin -D
    

    配置 plugins:

    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {
      plugins: {
        // 使用html模版
        new HtmlWebpackPlugin({
          // 配置html标题
          title: 'home',
          // 模版路径
          template: './src/index.html',
          // 压缩
          minify: true
        })
      }
    }
    

    此时想要配置的标题生效还需要在 html 中为 title 标签插值:

    <title><%= htmlWebpackPlugin.options.title %></title>
    

    除了 title 外,还可以配置诸如 meta 标签等。

    多页面

    上面的使用方法,在打包后只会有一个 html。对于多页面的需求其实也很简单,有多少个页面就 new 几次 htmlWebpackPlugin

    但是需要注意一点,入口配置的 js 是会被全部引入到 html 的。如果想某个 js 对应某个 html,可以配置插件的 chunks 选项。而且需要配置 filename 属性,因为 filename 属性默认是 index.html,同名会被覆盖。

    module.exports = {
      entry: {
        main: './src/js/main.js',
        news: './src/js/news.js'
      },
      output: {
        filename: '[name].[hash].js',
        path: path.join(__dirname, 'dist')
      },
      plugins: {
        new HtmlWebpackPlugin({
          template: './src/index.html',
          filename: 'index.html',
          minify: true,
          chunks: ['main']
        }),
        new HtmlWebpackPlugin({
          template: './src/news.html',
          filename: 'news.html',
          minify: true,
          chunks: ['news']
        }),
      }
    }
    

    es6转es5

    安装 babel 的相关模块:

    npm i babel-loader @babel/core @babel/preset-env -D
    

    @babel/core 是 babel 的核心模块,@babel/preset-env 内预设不同环境的语法转换插件,默认将 es6 转 es5。

    根目录下创建 .babelrc 文件:

    {
      "presets": ["@babel/preset-env"]
    }
    

    配置 loader:

    rules: [
      {
        test: /.js$/,
        exclude: /node_modules/, 
        loader: 'babel-loader'
      }
    ]
    

    解析 css

    安装 loader:

    npm i css-loader style-loader -D
    

    css-loader 只负责解析 css 文件,通常需要配合 style-loader 将解析的内容插入到页面,让样式生效。顺序是先解析后插入,所以 css-loader 放在最右边,第一个先执行。

    rules: [
      {
        test: /.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
    

    但要注意最终打包出来的并不是css文件,而是js。它是通过创建 style 标签去插入样式。

    分离css

    经过上面的 css 解析,打包出来的样式会混在 js 中。某些场景下,我们希望把 css 单独打包出来,这时可以使用 mini-css-extract-plugin 分离 css。

    安装 mini-css-extract-plugin:

    npm i mini-css-extract-plugin -D
    

    配置 plugins:

    const MiniCssExtractPlugin = require('mini-css-extract-plugin')
    
    module.exports = {
      plugins: [
        new MiniCssExtractPlugin({
          filename: 'css/[name].[hash].css',
          chunkFilename: 'css/[id].[hash].css'
        })
      ]
    }
    

    配置 loader:

    rules: [
      {
        test: /.css$/,
        use: [
          // 插入到页面中
          'style-loader',
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              // 为外部资源(如图像,文件等)指定自定义公共路径
              publicPath: '../',
            }
          },
          'css-loader',
        ]
      },
    ]
    

    经过上面配置后,打包后 css 就会被分离出来。

    但要注意如果 css 文件不是很大的话,分离出来效果可能会适得其反,因为这样会多一次文件请求,一般来说单个 css 文件超过 200kb 再考虑分离。

    css浏览器兼容前缀

    安装相关依赖:

    npm i postcss postcss-loader autoprefixer -D
    

    项目根目录下新建 postcss.config.js:

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

    package.json 新增 browserslist 配置:

    {
      "browserslist": [
        "defaults",
        "not ie < 11",
        "last 2 versions",
        "> 1%",
        "iOS 7",
        "last 3 iOS versions"
      ]
    }
    

    最后配置 postcss-loader

    rules: [
      {
        test: /.css$/,
        use: [
          'style-loader', 
          'css-loader',
          'postcss-loader'
         ]
      }
    ]
    

    处理前后对比:

    压缩 css

    webpack 内部默认只对 js 文件进行压缩。css 的压缩可以使用 optimize-css-assets-webpack-plugin 来完成。

    安装 optimize-css-assets-webpack-plugin:

    npm i optimize-css-assets-webpack-plugin -D
    

    配置 plugins:

    const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
    
    module.exports = {
      plugins: [
        new OptimizeCssAssetsWebpackPlugin()
      ]
    }
    

    压缩结果:

    解析图片

    项目中肯定少不了图片,对于图片资源,webpack 也有相应的 url-loader 来解析。url-loader 除了解析图片外,还可以将比较小的图片可以转为base64,减少线上对图片的请求。

    安装 url-loader:

    npm i url-loader -D
    

    配置 loader:

    rules: [
      {
        test: /.(jpg|png|jpeg|gif)$/,
          use: [{
            loader: 'url-loader',
            // 小于50k的图片转为base64,超出的打包到images文件夹
            options: {
              limit: 1024 * 50,
              outputPath: './images/',
              pulbicPath: './images/'
            }
        }]
      }
    ]
    

    配置完成后,只需要在入口文件内引入图片使用,webpack 就可以帮助我们把图片打包出来了。

    但有时候,图片链接是直接写到 html 中,这种情况 url-loader 无法解析。不慌,使用 html-loader 能完成这项需求。

    rules: [
      {
        test: /.(html)$/,
        use: [{
          loader: 'html-loader',
          options: {
            // 压缩html模板空格
            minimize: true,
            attributes: {
              // 配置需要解析的属性和标签
              list: [{
                tag: 'img',
                attribute: 'src',
                type: 'src',
              }]
            },
          }
        }]
      },
    ]
    

    注意这里 html-loader 只是起到解析的作用,需要配合 url-loader 或者 file-loader 去使用。也就是说解析模板的图片链接后,还是会走上面所配置的 url-loader 的流程。

    还有一点,使用 html-loader 后, html-webpack-plugin 在 html 中的插值会失效。

    其他类型资源解析

    解析其他资源和上面差不多,不过这里用到的是 file-loaderfile-loaderurl-loader 主要是将文件上的 import / require() 引入的资源解析为url,并将该资源发送到输出目录,区别在于 url-loader 能将资源转为 base64。

    安装 file-loader:

    npm i file-loader -D
    

    配置 loader:

    rules: [
      {
        test:/.(mp3)$/,
        use: [{
          loader: 'file-loader',
          options: {
            outputPath: './music/',
            pulbicPath: './music/'
          }
        }]
      },
      {
        test:/.(mp4)$/,
        use: [{
          loader: 'file-loader',
          options: {
            outputPath: './video/',
            pulbicPath: './video/'
          }
        }]
      }
    ]
    

    上面只是列举了部分,需要解析其他类型资源,参照上面的格式添加配置。

    解析 html 中的其他类型资源也和上面同理,使用 html-loader 配置对象的标签和属性即可。

    devServer 提高开发效率

    每次想运行项目时,都需要 build 完再去预览,这样的开发效率很低。

    官方为此提供了插件 webpack-dev-server,它可以本地开启一个服务器,通过访问本地服务器来预览项目,当项目文件发生变化时会热更新,无需再去手动刷新,以此提高开发效率。

    安装 webpack-dev-server

    npm i webpack-dev-server -D
    

    配置 webpack.config.js

    const path = require('path')
    
    module.exports = {
      devServer: {
        contentBase: path.join(__dirname, 'dist'),
        // 默认 8080
        port: 8000,
        compress: true,
        open: true,
      }
    }
    

    webpack-dev-server 用法和其他插件不同,它可以不添加到 plugins,只需将配置添加到 devServer 属性下即可。

    添加启动命令 package.json

    {
      "scripts": {
        "dev": "cross-env NODE_ENV=development webpack serve"
      },
    }
    

    命令行运行 npm run dev,就可以感受到飞一般的体验。

    复制文件到 dist

    对于一些不需要经过解析的文件,在打包后也想将它放到 dist 中,可以使用 copy-webpack-plugin

    安装 copy-webpack-plugin:

    npm i copy-webpack-plugin -D
    

    配置 plugins:

    const CopyPlugin = require('copy-webpack-plugin')
    
    module.exports = {
      plugins: [
        new CopyPlugin({
          patterns: [
            {
              // 资源路径
              from: 'src/json',
              // 目标文件夹
              to: 'json',
            }
          ]
        }),
      ]
    }
    

    打包前清除旧 dist

    打包后文件一般都会带有哈希值,它是根据文件的内容来生成的。由于名称不同,可能会导致 dist 残留有上一次打包的文件,如果每次都手动去清除显得不那么智能。利用 clean-webpack-plugin 可以帮助我们将上一次的 dist 清除,保证无冗余文件。

    安装 clean-webpack-plugin

    npm i clean-webpack-plugin -D
    

    配置 plugins:

    const CleanWebpackPlugin = require('clean-webpack-plugin')
    
    module.exports = {
      plugins: [
        new CleanWebpackPlugin()
      ]
    }
    

    插件会默认清除 output.path 的文件夹。

    自定义压缩选项

    webpack 从 v4.26.0 开始内置的压缩插件变为 terser-webpack-plugin。如果没有其他需求,自定义压缩插件也尽量保持与官方的一致。

    安装 terser-webpack-plugin:

    npm i terser-webpack-plugin -D
    

    配置 plugins:

    const TerserPlugin = reuqire('terser-webpack-plugin')
    
    module.exports = {
      optimization: {
        // 默认为 true
        minimize: true,
        minimizer: [
            new TerserPlugin()
        ]
      },
    }
    

    插件压缩配置可以查阅 terser-webpack-plugin 文档,在调用时自定义选项。

    像上面的 css 压缩插件也可以添加到 optimization.minimizer。与配置到 plugins 的区别是,配置到 plugins 的插件在任何情况都会去执行,而配置到 minimizer 中,只有在 minimize 属性开启时才会工作。

    这样做的目的便于通过 minimize 属性来统一控制压缩。

    const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
    const TerserPlugin = reuqire('terser-webpack-plugin')
    
    module.exports = {
      optimization: {
        // 默认为 true
        minimize: true,
        minimizer: [
            new TerserPlugin(),
            new OptimizeCssAssetsWebpackPlugin()
        ]
      },
    }
    

    注意如果你提供 minimizer 选项而没有使用 js 压缩插件,即使 webpack 内置 js 压缩,打包出来的 js 也不会被压缩。因为 webpack 压缩配置会被 minimizer 覆盖。

    排查错误的建议

    在使用 webpack 的过程中,这玩意偶尔会有些奇奇怪怪的报错。

    下面是我遇到的一些错误以及解决方法(仅供参考并不是万能法则):

    1. 一些 loader 和 plugin 在使用时,会依赖 webpack 的版本。如果使用过程发生错误,检查是否有版本不兼容的问题,可以尝试降一个版本。
    2. 重新安装依赖,有可能下载过程中,一些依赖会没装上。
    3. 查看使用文档,不同版本所传入的选项属性可能会不一样(被坑过) 。

    还有注意控制台的提示,一般根据错误提示都能猜出大概是什么问题。

    依赖版本和完整配置

    项目结构:

    依赖版本:

    {
        "@babel/core": "^7.12.3",
        "@babel/preset-env": "^7.12.1",
        "autoprefixer": "^10.0.1",
        "babel-loader": "^8.1.0",
        "clean-webpack-plugin": "^3.0.0",
        "copy-webpack-plugin": "^6.3.0",
        "cross-env": "^7.0.2",
        "css-loader": "^5.0.0",
        "file-loader": "^6.2.0",
        "html-loader": "^1.3.2",
        "html-webpack-plugin": "^4.5.0",
        "mini-css-extract-plugin": "^0.9.0",
        "postcss": "^8.1.6",
        "postcss-loader": "^4.0.4",
        "style-loader": "^2.0.0",
        "url-loader": "^4.1.1",
        "webpack": "^4.44.2",
        "webpack-cli": "^4.2.0",
        "webpack-dev-server": "^3.11.0"
    }
    

    webpack.config.js:

    const path = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const CopyPlugin = require('copy-webpack-plugin')
    const MiniCssExtractPlugin = require('mini-css-extract-plugin')
    const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
    const { CleanWebpackPlugin } = require('clean-webpack-plugin')
    
    /** @type {import('webpack').Configuration} */
    module.exports = {
      devtool: process.env.NODE_ENV === 'development' ? 'source-map' : 'none',
      entry: {
        main: './src/js/main.js'
      },
      output: {
        filename: '[name].[hash].js',
        path: path.join(__dirname, 'dist')
      },
      devServer: {
        contentBase: path.join(__dirname, 'dist'),
        port: 8000,
        compress: true,
        open: true,
      },
      plugins: [
        // 清除上一次的打包内容
        new CleanWebpackPlugin(),
        // 复制文件到dist
        new CopyPlugin({
          patterns: [
            {
              from: 'src/music',
              to: 'music',
            }
          ]
        }),
        // 使用html模版
        new HtmlWebpackPlugin({
          template: './src/index.html',
          minify: true
        }),
        // 分离css
        new MiniCssExtractPlugin({
          filename: 'css/[name].[hash].css',
          chunkFilename: 'css/[id].[hash].css'
        }),
        // 压缩css
        new OptimizeCssAssetsWebpackPlugin()
      ],
      module: {
        rules: [
          // 解析js(es6转es5)
          {
            test: /.js$/,
            exclude: /node_modules/, 
            loader: 'babel-loader'
          },
          {
            test: /.css$/,
            use: [
              'style-loader',
              {
                loader: MiniCssExtractPlugin.loader,
                options: {
                  publicPath: '../',
                }
              },
              'css-loader',
              'postcss-loader'
            ]
          },
          {
            test: /.(html)$/,
            use: [{
              // 主要为了解析html中的img图片路径 需要配合url-loader或file-loader使用
              loader: 'html-loader',
              options: {
                attributes: {
                  list: [{
                    tag: 'img',
                    attribute: 'src',
                    type: 'src',
                  },{
                    tag: 'source',
                    attribute: 'src',
                    type: 'src',
                  }]
                },
                minimize: true
              }
            }]
          },
          // 解析图片
          {
            test: /.(jpg|png|gif|svg)$/,
            use: [{
              loader: 'url-loader',
              // 小于50k的图片转为base64,超出的打包到images文件夹
              options: {
                limit: 1024 * 50,
                outputPath: './images/',
                pulbicPath: './images/'
              }
            }]
          },
          // 解析其他类型文件(如字体)
          {
            test: /.(eot|ttf)$/,
            use: ['file-loader']
          },
          {
            test: /.(mp3)$/,
            use: [{
              loader: 'file-loader',
              options: {
                outputPath: './music/',
                pulbicPath: './music/'
              }
            }]
          }
        ]
      },
    }
    

    最后

    由于单页项目简单,配置项比较朴实无华,本文主要是些基础配置。不过套路都差不多,根据项目的需求去选择 loader 和 plugin。更多的还是要了解这些插件的作用和使用方法,以及其他常用的插件。

  • 相关阅读:
    231. Power of Two
    204. Count Primes
    205. Isomorphic Strings
    203. Remove Linked List Elements
    179. Largest Number
    922. Sort Array By Parity II
    350. Intersection of Two Arrays II
    242. Valid Anagram
    164. Maximum Gap
    147. Insertion Sort List
  • 原文地址:https://www.cnblogs.com/chanwahfung/p/14153874.html
Copyright © 2011-2022 走看看