Webpack5.0手把手教你丛零搭建(基础篇)

959 阅读10分钟

Webpack5.0手把手教你丛零搭建

前言

开发过程中一直冲锋陷阵于业务,对工程化的项目一直是享受其中,对webpack架构了解甚少,遇到工程化问题一头雾水,所以针对现有webpack5.70.0版本,丛零到高级进阶篇统统学习了一遍,本篇主要对学习过程做输出。

image.png

了解webpack

  • 安装

npm install webpack webpack-cli webpack-dev-server -D

编译:npx webpack 启动 npx webpack server

webpack是什么

打包器: webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler) 。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph) ,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle

构建工具:主要理解一下这个前端资源是哪些资源。这些前端资源就是浏览器不认识的 web 资源, 比如 sass、less、ts,包括 js 里的高级语法。这些资源要能够在浏览器中正常工作,必须一一经过编译处理。而 webpack 就是可以集成这些编译工具的一个总的构建工具。

为什么要用webpack

回答这个问题,可以和还没有 Webpack、没有构建工具时对比一下,就能明显地感觉出来了。这里就来列举一下不使用构建工具时的痛点。

web 开发时调用后端接口跨域,需要其他工具代理或者其他方式规避。 改动代码后要手动刷新浏览器,如果做了缓存还需要清缓存刷新。 因为 js 和 css 的兼容性问题,很多新语法学习了却不能使用,无论是开发效率和个人成长都受影响。 打包问题。需要使用额外的平台如 jekins 打包,自己编写打包脚本,对各个环节如压缩图片,打包 js、打包 css 都要一一处理。 ...... 而这些问题,Webpack 都提供了解决方案,你只需要做一些简单的配置就可以上手使用了。当然,Webpack 做的还不止这些,下面就来一一介绍。

webpack核心配置

  • entry(入口)引入根目录文件路径
    entry: "./src/index.js",
    // array方式:多入口,所有入口文件最终只会形成一个chunk,输出出去只有一个bundle文件
    entry: ["./src/index.js", "./src/test.js"],
    // object:多入口,有几个入口文件就形成几个chunk,输出几个bundle文件。此时chunk的名称就是对象key值
    entry:{
        index:"./src/index.js",
        test:"./src/test.js",
    }

  • output(输出)属性告诉 webpack 在哪里输出它所创建的 bundles以及如何命名这些文件,默认值为 ./dist
      // contenthash读取缓存,并能发现更新过的文件并编译成最新到
      filename: 'script/[name].[contenthash].js',
      path: path.resolve(__dirname, './dist'),
      clean: true, //清除每一次打包前的dist。相当于clean-webpack-plugin插件的作   用,webpack5新增。
      assetModuleFilename: 'images/test[contenthash][ext]',
      publicPath: 'http://localhost:8080/' //配置域名
    },

拆分生产环境与开发环境配置

  • 公共路径

publicPath 配置选项在各种场景中都非常有用。你可以通过它来指定应用程序中所有资源都基础路径。

-基于环境设置

在开发环境中,我们通常有一个assets/文件,它与索引页面位于同一级别。这没太大问题,但是如果我们将所有静态资源托管至cdn,然后想在生产环境中想要解决这个问题 可以直接使用一个environment variable(环境变量)。假设我们有一个变量ASSET_PATH: cosnt ASSET_PATH = process.env.ASSET_PATH||'/‘; 设置生产环境下打包完成后压缩代码 const TerserPlugin = require('terser-webpack-plugin'); 安装 npm install terser-webpack-plugin -D

  • loader loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。
      rules: [
        { //用于配置配文件
          test: /\.png$/,
          type: 'asset/resource',
          generator: {  //contenthash动态hash名称
            filename: 'images/test[contenthash][ext]'
          }
        },
        {
          test: /\.svg$/,
          type: 'asset/inline',
        },
        {
          test: /\.txt$/,
          type: 'asset/source'
        },
        {
          test: /\.jpeg$/,
          type: 'asset',
          parser: {
            dataUrlCondition: {
              maxSize: 4 * 1024 * 1024
            }
          }
        },
        {//创建style标签,将js中的样式资源(就是css-loader转化成的字符串)拿过来,添加到页面head标签生效
        //执行顺序为从左向右执行,从下至上执行
          test: /\.(css|less)$/,
          use: [MinCssExtractPlugin.loader, 'css-loader', 'less-loader']
        },
        {
          test: /\.(woff|wof2|eot|ttf|otf)$/,
          type: 'asset/resource',
        },
        {
          test: /\.(csv|tsv)$/,
          use: 'csv-loader',
        },
        {
          test: /\.xml$/,
          use: 'xml-loader',
        },
        {
          test: /\.toml$/,
          type: 'json',
          parser: {
            parse: toml.parse
          }
        },
        {
          test: /\.yaml$/,
          type: 'json',
          parser: {
            parse: yaml.parse
          }
        },
        {
          test: /\.json5$/,
          type: 'json',
          parser: {
            parse: json5.parse
          }
        },
        { 
          test: /\.js$/,
          //打包去除node_modules
          exclude: /node_modules/,
          use: {
          //js兼容处理 babel
            loader: 'babel-loader',
            options: {
            // 预设:指示babel做怎么样的兼容处理
              presets: ['@babel/preset-env'],
              plugins: [
                [
                  '@babel/plugin-transform-runtime'
                ]
              ]
            }
          }
        }
      ]
    },

Webpack5.0新增资源模块(asset module),它是一种模块类型,允许使用资源文件(字体,图标等)而无需 配置额外 loader。支持以下四个配置 asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。 asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。 asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。 asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资 源体积限制实现。

Webpack4使用file-loader实现 { test: /.(eot|svg|ttf|woff|)/, type: "asset/resource", generator: { // 输出文件位置以及文件名 filename: "fonts/[name][ext]" }, }, // Webpack4使用url-loader实现 { //处理图片资源 test: /.(jpg|png|gif|)/, type: "asset", generator: { // 输出文件位置以及文件名 filename: "images/[name][ext]" }, parser: { dataUrlCondition: { maxSize: 10 * 1024 //超过10kb不转base64 } } }, ],

  • npm install -D babel-loader @babel/preset-env

  • webpack是做js文件的打包,而并不能做js代码的转换,例如es6转换es5, 高版本浏览器可解析,低版本就会报错

  • babel-loader:在webpack里应用Babel解析es6的桥梁,兼容低版本浏览器

  • @Babel/core: babel核心模块

  • @Babel/preset-env: babel预设,一组Babel插件的集合 在webpack 配置中,需要将将babel-loader添加到module列表中如下:

    {
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env'],
          plugins: [
            [
              '@babel/plugin-transform-runtime'
            ]
          ]
        }
      }
    }
    

在代码当中用到了async await es6语法,需要安装regeneratorRuntime插件解决转换

  • plugin

loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。[插件接口(www.webpackjs.com/api/plugins… 想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建它的一个实例。

//HtmlWebpackPlugin帮助你创建html文件,并自动引入打包输出的bundles文件。支持html压缩。
const HtmlWebpackPlugin = require("html-webpack-plugin");

//该插件将CSS提取到单独的文件中。它会为每个chunk创造一个css文件。需配合loader一起使用
const MinCssExtractPlugin = require('mini-css-extract-plugin');

//这个插件也可以用来压缩 css 文件。和 optimize-css-assets-webpack-plugin 加 cssnano 的效果是一样的。
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

//压缩js代码。
const TerserPlugin = require('terser-webpack-plugin');

const { CleanWebpackPlugin } = require("clean-webpack-plugin"); 
从webpack5开始,webpack内置了该功能,只要在ouput中配置clear为true即可

 plugins: [
      new HtmlWebpackPlugin({
        template: './index.html', //要产出的根目录文件
        filename: 'app.html', //产出文件的名字
        inject: 'body',// 把生成的script标签放到body里
      }),
      new MinCssExtractPlugin({
        filename: 'styles/[contenthash].css',
      })
    ],

  • mode

development:(开发)会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development. 为模块和 chunk 启用有效的名。


production:(生产)会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin 和 TerserPlugin 。

DefinePlugin:定义全局变量process.env.NODE_ENV,区分程序运行状态。 FlagDependencyUsagePlugin:标记没有用到的依赖。 FlagIncludedChunksPlugin:标记chunks,防止chunks多次加载。 ModuleConcatenationPlugin:作用域提升(scope hosting),预编译功能,提升或者预编译所有模块到一个闭包中,提升代码在浏览器中的执行速度。 NoEmitOnErrorsPlugin:防止程序报错,就算有错误也继续编译。

  • devserver

devServer: { 
    // 运行代码的目录 
    contentBase: path.resolve(__dirname, "build"), 
    // 为每个静态文件开启gzip压缩 
    compress: true, 
    host: true,//热更新
    port: 8000,
    open: true, 
    // 自动打开浏览器
    hot: true,
    //如果我们的应用是一个SPA(但页面应用),当路由到/some时(可以直接只地址栏里输入)会发现此时刷新页面后,控制台会报错。
    historyApiFallback: true,
    liveReload: true,//热替换
    headers: {
      'X-Access-Token': 'lktesttoken'
    }
    //开启HMR功能 
    // 设置代理
    proxy: { // 一旦devServer(5000端口)接收到/api/xxx的请求,就会用devServer起的服务把请求转发到另外一个服务器(3000) // 以此来解决开发中的跨域问题
    api: { target: "htttp://localhost:3000", // 发送请求时,请求路径重写:将/api/xxx --> /xxx (去掉/api) 
    pathRewrite: { "^api": "", },
        }, 
    }, 
}

//生产环境下
    optimization: {
      minimizer: [
        new CssMinimizerPlugin(),
        new TerserPlugin()
      ],
      splitChunks: { //用于优化,可以帮助公共代码的抽离,比如工具库,loadsh,ahooks等
        cacheGroups: { // 引用第三方文件用于缓存浏览器(由于这种类库不频繁更新,可以提高我们打开速度,以及节省我们的网络流量)
          vendor: {
            test: /[//\]node_modules[\\/]/,
            name: 'vendors',
            chunks: 'all'
          }
        }
      }
    }

webpack 打包优化

devtool

  • eval:每个module会封装到eval里包裹起来执行,并且会在末尾追加注释//@sourceURL。
  • source-map: 生成一个source-map
  • hidden-source-map 生成一个source-map一样,但不会在bundle末尾添加注释。
  • inline-source-map 生成DataUrl形式source-map文件。
  • eval-source-map: 每个module都会从eval来执行,并且生成一个DataUrl形式的sourceMap。
  • chep-source-map:生成一个没有列信息(column-mappings)的sourceMap文件,不包含loader的sourcemap(譬如Babloaderel的sourcemap)
  • cheap-module-source-map:生成一个没有列信息(column-mappings)的source-Map文件,同时的soucemap也被简化为只包含对应行的。
  • 要注意的是,生产环境我们一般不会开启source-map功能,主要有两个原因:

1、通过bundle和sourcemap文件,可以反编译出源码---也就是说,线上产物有soucemap文件的话,那就意味有暴露源码的风险。 2、我么观察到,sourcemap文件的体积相对比较巨大,这跟我们生产环境的追求不同(生产环境追求更小更轻量的bundle)。

webpack 配置文件抽离公共配置

image.png

  • 安装 npm install webpack-merge -D //引用merge pai
  • const { merge } = require('webpack-merge');

//抽离出开发与生产公共部分

  • const commonConfig = require('./webpack.config.common');

//只编写生产环境的配置

  • const productionConfig = require('./webpack.config.prod');

//只编写开发环境下的配置

  • const developmentConfig = require('./webpack.config.dev');
  switch (true) {
  //根据编译环境 产出不同环境的产物
    case env.development:
      return merge(commonConfig, developmentConfig)
    case env.production:
      return merge(commonConfig, productionConfig)
    default:
      return new Error('没有匹配到config')
  }
}

代码分离 减小入口文件的大小,增加编译速度

{
              入口起点(使用entry配置手动分离代码)
 代码分离      防止重复(使用entry dependenvies或者SplitChunksPlugin 去重和分离代码)
              动态导入(通过模块的内联函数调用来分离代码)
}

防止代码重复(优化)

  • 入口依赖

配置 dependOn Option选项,这样可以在多个chunk之间共享模块

{
  import: './src/index.js',
  dependOn: 'shared'
},
another: {
  import: './src/another-module.js',
  dependOn: 'shared'
},
shared: 'lodash'

缓存

  • 缓存自己业务内的代码
  • webpack编译加载时候能够把文件缓存到客户端,以及发现新的文件变化时候,会请求到新的文件。
  • 输出的文件名字: filename: '[name].[contenthash].js'

我们可以通过替换output.filename中的substitutions设置,来定义输出文件的名称。webpack提供来一种使用名称为substion(可替换模板字符串)的方式,通过带括号

  • 缓存第三方库的代码,lodash\ahooks...

将第三方库的(libray)提到单独的vendor chun文件中,是比较推荐的方法,这是因为,他们很少像本地代码频繁修改,通过实现以上步骤,利用client缓存机制,命中缓存来消除请求 optimization.splitChunks添加cacheGroups参数构建.

持续更新进阶篇。。。 不足之处希望在线提出批评与指导。吸取广大同胞建议,做到最好。。。