最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Webpack入门 - 从0开始搭建项目配置

    正文概述 掘金(WahFung)   2020-12-20   457

    前言

    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入门 - 从0开始搭建项目配置

    环境变量

    一般开发中会分开发和生产两种环境,而 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 就像一个地图, 能够对应源代码的位置。这个选项能够帮助开发者增强调试过程,准确定位错误。

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

    Webpack入门 - 从0开始搭建项目配置

    在预览时,触发错误:

    Webpack入门 - 从0开始搭建项目配置

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

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

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

    Webpack入门 - 从0开始搭建项目配置

    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 标签去插入样式。

    Webpack入门 - 从0开始搭建项目配置

    分离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 就会被分离出来。

    Webpack入门 - 从0开始搭建项目配置

    但要注意如果 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'
         ]
      }
    ]
    

    处理前后对比:

    Webpack入门 - 从0开始搭建项目配置

    压缩 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入门 - 从0开始搭建项目配置

    解析图片

    项目中肯定少不了图片,对于图片资源,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 文档,在调用时自定义选项。

    添加到 minimizer 和 plugins 的区别

    像上面的 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. 查看使用文档,不同版本所传入的选项属性可能会不一样(被坑过) 。

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

    依赖版本和完整配置

    项目结构:

    Webpack入门 - 从0开始搭建项目配置

    依赖版本:

    {
        "@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。更多的还是要了解这些插件的作用和使用方法,以及其他常用的插件。


    起源地下载网 » Webpack入门 - 从0开始搭建项目配置

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元