学习 Webpack5 之路(实践篇)

几何心凉
2022年度博客之星前端领域TOP 1
博客专家认证
2021-11-02 09:36:01
加精

前言

 

本篇将从实践出发,在第一章节《基础配置》中使用 webpack 搭建一个基础的支持模块化开发的项目,在第二章节《进阶配置》中使用 webpack 搭建一个 SASS + TS + React 的项目。

本文依赖的 webpack 版本信息如下:

  • webpack-cli\@4.7.2[2]

  • webpack\@5.46.0[3]

一、基础配置

接下来一起配置一个基础的 Webpack。

将支持以下功能:

  • 分离开发环境、生产环境配置;

  • 模块化开发;

  • sourceMap 定位警告和错误;

  • 动态生成引入 bundle.js 的 HTML5 文件;

  • 实时编译;

  • 封装编译、打包命令。

想直接看配置的同学 -> 本文源码地址:webpack Demo0[4]

1. 新建项目

新建一个空项目:

// 新建 webpack-demo 文件夹
mkdir webpack-demo

// 进入 webpack-demo 目录
cd ./webpack-demo

// 初始化项目
npm init -y
复制代码

新建 2 个 js 文件,并进行模块化开发:

// 进入项目目录
cd ./webpack-demo

// 创建 src 文件夹
mkdir src

// 创建 js文件
touch index.js
touch hello.js
复制代码

index.js:

// index.js

import './hello.js'

console.log('index')
复制代码

hello.js:

// hello.js
console.log('hello webpack')
复制代码

项目结构如下:

- src
    - index.js
    - hello.js
- package.json
- node_modules
复制代码

2. 安装

  1. 安装 Node

Node 需要是最新版本,推荐使用 nvm 来管理 Node 版本。

将 Node.js 更新到最新版本,也有助于提高性能。除此之外,将你的 package 管理工具(例如 npm 或者 yarn)更新到最新版本,也有助于提高性能。较新的版本能够建立更高效的模块树以及提高解析速度。

  • Node:安装地址[5]

  • nvm:安装地址[6]

我安装的版本信息如下:

  • node v14.17.3

  • npm v6.14.13)

  1. 安装 webpack
    
npm install webpack webpack-cli --save-dev
复制代码

3. 新建配置文件

development(开发环境) 和 production(生产环境) 这两个环境下的构建目标存在着巨大差异。为代码清晰简明,为每个环境编写彼此独立的 webpack 配置

// 进入项目目录
cd ./webpack-demo

// 创建 config 目录
mkdir config

// 进入 config 目录
cd ./config

// 创建通用环境配置文件
touch webpack.common.js

// 创建开发环境配置文件
touch webpack.dev.js

// 创建生产环境配置文件
touch webpack.prod.js
复制代码

webpack-merge

使用 webpack-marge 合并通用配置和特定环境配置。

安装 webpack-merge:

npm i webpack-merge -D
复制代码

通用环境配置:

// webpack.common.js

module.exports = {} // 暂不添加配置

复制代码

开发环境配置:

// webpack.dev.js

const { merge } = require('webpack-merge')
const common = require('./webpack.common')

module.exports = merge(common, {}) // 暂不添加配置

复制代码

生产环境配置:

// webpack.prod.js

const { merge } = require('webpack-merge')
const common = require('./webpack.common')

module.exports = merge(common, {}) // 暂不添加配置

复制代码

项目结构如下:

- config
    - webpack.common.js
    - webpack.dev.js
    - webpack.prod.js
- src
    - index.js
    - hello.js
- package.json
- node_modules
复制代码

4. 入口(entry)

入口起点(entry point) 指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图\(dependency graph\)[7] 的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

在本例中,使用 src/index.js 作为项目入口,webpack 以 src/index.js 为起点,查找所有依赖的模块。

修改 webpack.commom.js:

module.exports = merge(common, {
  // 入口
  entry: {
    index: './src/index.js',
  },
})
复制代码

5. 输出(output)

output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。

生产环境的 output 需要通过 contenthash 值来区分版本和变动,可达到清缓存的效果,而本地环境为了构建效率,则不引人 contenthash

新增 paths.js,封装路径方法:

const fs = require('fs')
const path = require('path')

const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);

module.exports = {
  resolveApp,
  appPublic: resolveApp('public'),
  appHtml: resolveApp('public/index.html'),
  appSrc: resolveApp('src'),
  appDist: resolveApp('dist'),
  appTsConfig: resolveApp('tsconfig.json'),
}
复制代码

修改开发环境配置文件 webpack.dev.js:

module.exports =  merge(common, {
  // 输出
  output: {
    // bundle 文件名称
    filename: '[name].bundle.js',
    
    // bundle 文件路径
    path: resolveApp('dist'),
    
    // 编译前清除目录
    clean: true
  },
})
复制代码

修改生产环境配置文件 webpack.prod.js:

module.exports =  merge(common, {
  // 输出
  output: {
    // bundle 文件名称 【只有这里和开发环境不一样】
    filename: '[name].[contenthash].bundle.js',
    
    // bundle 文件路径
    path: resolveApp('dist'),
    
    // 编译前清除目录
    clean: true
  },
})
复制代码

上述 filename 的占位符解释如下:

  • [name] - chunk name(例如 [name].js -> app.js)。如果 chunk 没有名称,则会使用其 id 作为名称

  • [contenthash] - 输出文件内容的 md4-hash(例如 [contenthash].js -> 4ea6ff1de66c537eb9b2.js

6. 模式(mode)

通过 mode 配置选项,告知 webpack 使用相应模式的内置优化。

选项描述
development会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development. 为模块和 chunk 启用有效的名。
production会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePluginFlagIncludedChunksPluginModuleConcatenationPluginNoEmitOnErrorsPlugin 和 TerserPlugin 。

修改开发环境配置文件 webpack.dev.js:

module.exports =  merge(common, {
  // 开发模式
  mode: 'development',
})
复制代码

修改生产环境配置文件 webpack.prod.js:

module.exports =  merge(common, {
  // 生产模式
  mode: 'production',
})
复制代码

7. Source Map

当 webpack 打包源代码时,可能会很难追踪到 error 和 warning 在源代码中的原始位置。

为了更容易地追踪 error 和 warning,JavaScript 提供了 source maps[8] 功能,可以将编译后的代码映射回原始源代码。

修改开发环境配置文件 webpack.dev.js:

module.exports =  merge(common, {
  // 开发工具,开启 source map,编译调试
  devtool: 'eval-cheap-module-source-map',
})
复制代码

source map 有许多 可用选项[9]。本例选择的是 eval-cheap-module-source-map

注:为加快生产环境打包速度,不为生产环境配置 devtool。

完成上述配置后,可以通过 npx webpack \--config config/webpack.prod.js 打包编译。

编译后,会生成这样的目录结构:

8. HtmlWebpackPlugin

npx webpack \--config config/webpack.prod.js 后仅生成了 bundle.js,我们还需要一个 HTML5 文件,用来动态引入打包生成的 bundle 文件。

引入 HtmlWebpackPlugin 插件,生成一个 HTML5 文件, 其中包括使用 script 标签的 body 中的所有 webpack 包。

安装:

npm install --save-dev html-webpack-plugin
复制代码

修改通用环境配置文件 webpack.commom.js:

module.exports = {
  plugins: [
    // 生成html,自动引入所有bundle
    new HtmlWebpackPlugin({
      title: 'release_v0',
    }),
  ],
}
复制代码

重新 webpack 编译 npx webpack \--config config/webpack.prod.js,生成的目录结构如下:

 

新生成了 index.html,动态引入了 bundle.js 文件:

<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8" />
  <title>release_v0</title>
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <script defer="defer" src="index.468d741515bfc390d1ac.bundle.js"></script>
 </head>
 <body></body>
</html>
复制代码

9. DevServer

在每次编译代码时,手动运行 npx webpack \--config config/webpack.prod.js 会显得很麻烦,webpack-dev-server[10] 帮助我们在代码发生变化后自动编译代码。

webpack-dev-server 提供了一个基本的 web server,并且具有实时重新加载功能。

webpack-dev-server 默认配置 conpress: true,为每个静态文件开启 gzip compression[11]。

安装:

npm install --save-dev webpack-dev-server
复制代码

修改开发环境配置文件 webpack.dev.js:

module.exports = merge(common, {
  devServer: {
    // 告诉服务器从哪里提供内容,只有在你想要提供静态文件时才需要。
    contentBase: './dist',
  },
})
复制代码

完成上述配置后,可以通过 npx webpack serve \--open \--config config/webpack.dev.js 实时编译。

效果如图:

 

10.执行命令

上述配置文件完成后,优化 webpack 的实时编译、打包编译指令。

通过 cross-env 配置环境变量,区分开发环境和生产环境。

安装:

npm install --save-dev cross-env
复制代码

修改 package.json:

{
    "scripts": {
        "dev": "cross-env NODE_ENV=development webpack serve --open --config config/webpack.dev.js",
        "build": "cross-env NODE_ENV=production webpack --config config/webpack.prod.js"
      },
}
复制代码

现在可以运行 webpack 指令:

  • npm run dev:本地构建;

  • npm run build:生产打包。

以上我们完成了一个基于 webpack 编译的支持模块化开发的简单项目。

源码地址:webpack Demo0[12]

二、进阶配置

本章节将继续完善配置,在上述配置基础上,用 Webpack 搭建一个 SASS + TS + React 的项目。

将支持以下功能:

  • 加载图片;

  • 加载字体;

  • 加载 CSS;

  • 使用 SASS;

  • 使用 PostCSS,并自动为 CSS 规则添加前缀,解析最新的 CSS 语法,引入 css-modules 解决全局命名冲突问题;

  • 使用 React;

  • 使用 TypeScript。

想直接看配置的同学 -> 本文源码地址:webpack Demo1[13]

1. 加载图片(Image)

在 webpack 5 中,可以使用内置的 Asset Modules[14],将 images 图像混入我们的系统中。

修改通用环境配置文件 webpack.commom.js:

const paths = require('./paths');
module.exports = {
    module: {
        rules: [
          {
            test: /\.(png|svg|jpg|jpeg|gif)$/i,
            include: paths.appSrc,
            type: 'asset/resource',
          },
        ],
      },
}
复制代码

在实际开发过程中,推荐将大图片上传至 CDN,提高加载速度。

2. 加载字体(Font)

使用 Asset Modules[15] 接收字体文件。

修改通用环境配置文件 webpack.commom.js:

module.exports = {
    module: {
        rules: [
            {
               test: /.(woff|woff2|eot|ttf|otf)$/i,
               include: [
                  resolveApp('src'),
                ],
               type: 'asset/resource',
             },
         ]
     }
 }
     
复制代码

在这里我发现,我不新增对 ttf 文件的配置,也能够引入字体不报错,不知道是什么问题,先记录一下,有知道原因的大佬移步评论区。

在实际开发过程中,推荐将字体文件压缩上传至 CDN,提高加载速度。如配置字体的文字是固定的,还可以针对固定的文字生成字体文件,可以大幅缩小字体文件体积。

3. 加载 CSS

为了在 JavaScript 模块中 import 一个 CSS 文件,需要安装并配置 style-loader[16] 和 css-loader[17]。

3.1 style-loader[18]

style-loader 用于将 CSS 插入到 DOM 中,通过使用多个 <style></style> 自动把 styles 插入到 DOM 中.

3.2 css-loader[19]

css-loader 对 @import 和 url() 进行处理,就像 js 解析 import/require() 一样,让 CSS 也能模块化开发。

3.3 安装配置

安装 CSS 相关依赖:

npm install --save-dev style-loader css-loader
复制代码

修改通用环境配置文件 webpack.commom.js:

const paths = require('./paths');
module.exports = {
    module: {
        rules: [
            {
                test: /\.css$/,
                include: paths.appSrc,
                use: [
                  // 将 JS 字符串生成为 style 节点
                  'style-loader',
                  // 将 CSS 转化成 CommonJS 模块
                  'css-loader',
                ],
              },
          ]
      }
  }
复制代码

4. 使用 SASS

4.1 Sass[20]

Sass[21] 是一款强化 CSS 的辅助工具,它在 CSS 语法的基础上增加了变量、嵌套、混合、导入等高级功能。

4.2 sass-loader[22]

sass-loader[23] 加载 Sass/SCSS 文件并将他们编译为 CSS。

4.3 安装配置

安装 SASS 相关依赖:

npm install --save-dev sass-loader sass 
复制代码

修改通用环境配置文件 webpack.commom.js:

const paths = require('./paths');
module.exports = {
    module: {
        rules: [
        {
        test: /.(scss|sass)$/,
        include: paths.appSrc,
        use: [
          // 将 JS 字符串生成为 style 节点
          'style-loader',
          // 将 CSS 转化成 CommonJS 模块
          'css-loader',
          // 将 Sass 编译成 CSS
          'sass-loader',
        ],
      },
复制代码

5. 使用 PostCSS

5.1 PostCSS[24]

PostCSS[25] 是一个用 JavaScript 工具和插件转换 CSS 代码的工具。

  • 可以自动为 CSS 规则添加前缀;

  • 将最新的 CSS 语法转换成大多数浏览器都能理解的语法;

  • css-modules 解决全局命名冲突问题。

5.2 postcss-loader[26]

postcss-loader[27] 使用 PostCSS[28] 处理 CSS 的 loader。

5.3 安装配置

安装 PostCSS 相关依赖:

npm install --save-dev postcss-loader postcss postcss-preset-env
复制代码

修改通用环境配置文件 webpack.commom.js:

const paths = require('./paths');
module.exports = {
    module: {
        rules: [
          {
            test: /\.module\.(scss|sass)$/,
            include: paths.appSrc,
            use: [
              // 将 JS 字符串生成为 style 节点
              'style-loader',
              // 将 CSS 转化成 CommonJS 模块
              {
                loader: 'css-loader',
                options: {
                  // Enable CSS Modules features
                  modules: true,
                  importLoaders: 2,
                  // 0 => no loaders (default);
                  // 1 => postcss-loader;
                  // 2 => postcss-loader, sass-loader
                },
              },
              // 将 PostCSS 编译成 CSS
              {
                loader: 'postcss-loader',
                options: {
                  postcssOptions: {
                    plugins: [
                      [
                        // postcss-preset-env 包含 autoprefixer
                        'postcss-preset-env',
                      ],
                    ],
                  },
                },
              },
              // 将 Sass 编译成 CSS
              'sass-loader',
            ],
          },
        ],
      },
}
复制代码

为提升构建效率,为 loader 指定 include,通过使用 include 字段,仅将 loader 应用在实际需要将其转换的模块。

5. 使用 React + TypeScript

为了让项目的配置灵活性更高,不使用 create-reate-app 一键搭建项目,而是手动搭建 React 对应的配置项。

安装 React 相关:

npm i react react-dom @types/react @types/react-dom -D
复制代码

安装 TypeScript 相关:

npm i -D typescript esbuild-loader
复制代码

为提高性能,摒弃了传统的 ts-loader,选择最新的 esbuild-loader。

修改通用环境配置文件 webpack.commom.js:

const paths = require('./paths');
 module.exports = {
    resolve: {
        extensions: ['.tsx', '.ts', '.js'],
    },
    module: {
        rules: [
            {
                test: /\.(js|ts|jsx|tsx)$/,
                include: paths.appSrc,
                use: [
                  {
                    loader: 'esbuild-loader',
                    options: {
                      loader: 'tsx',
                      target: 'es2015',
                    },
                  }
                ]
              },
         ]
     }
 }
复制代码

TypeScript[29] 是 JavaScript 的超集,为其增加了类型系统,可以编译为普通 JavaScript 代码。

为兼容 TypeScript 文件,新增 typescript 配置文件 tsconfig.json:

{
    "compilerOptions": {
        "outDir": "./dist/",
        "noImplicitAny": true,
        "module": "es6",
        "target": "es5",
        "jsx": "react",
        "allowJs": true,
        "moduleResolution": "node",
        "allowSyntheticDefaultImports": true,
        "esModuleInterop": true,
      }
}
复制代码

如果想在 TypeScript 中保留如import _ from 'lodash';的语法被让它作为一种默认的导入方式,需要在文件 tsconfig.json 中设置 "allowSyntheticDefaultImports" : true 和 "esModuleInterop" : true 。

注意:这儿有坑

1. "allowSyntheticDefaultImports": true 配置

TypeScript 配置文件 tsconfig.json 需要加 "allowSyntheticDefaultImports": true 配置,否则会提示 can only be default-imported using the 'allowSyntheticDefaultImports' flag

{
    "compilerOptions": {
        "allowSyntheticDefaultImports": true
    },
}
复制代码

不加 "allowSyntheticDefaultImports": true 的加报错信息如下:

2. tsx 和 jsx 不能混合使用

在 tsx 中引入 jsx 文件报错如下:

 

以上我们完成了一个基于 webpack 编译的 SASS + TS + React 项目。

源码地址:webpack Demo1[30]:https://github.com/jiaozitang/webpack-demo/tree/release_v1

三、总结

本文从 Webpack 基础配置、Webpack 进阶配置 2 个角度进行讲述,从 Webpack 实践着手,和你一起了解 Webpack。

下一篇《学习 Webpack5 之路(优化篇)》将从继续优化项目配置,尝试搭建一个最优的 Webpack 配置,敬请期待。

本文源码:

  • webpack Demo0[31]:https://github.com/jiaozitang/webpack-demo/tree/release_v0

  • webpack Demo1[32]:https://github.com/jiaozitang/webpack-demo/tree/release_v1

希望能对你有所帮助,感谢阅读~

别忘了点个赞鼓励一下我哦,笔芯❤️

 

关于本文

来源:清汤饺子

https://juejin.cn/post/6991774994552324133

...全文
492 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

241,569

社区成员

发帖
与我相关
我的任务
社区描述
欢迎加入几何心凉的前端社区,本社区活动丰富可以拿到众多周边礼物,本社区还对接Vue技能树可以更加系统的进行学习,还为大家定期举办博主成长计划,助您赢在CSDN同时带您遨游在前端技术的海洋中!!
前端学习经验分享 个人社区 北京·丰台区
社区管理员
  • 几何心凉
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

诚挚的邀请大家加入几何心凉社区,在这里您可以结实挚友、提升技术、分享经验、成就自己

  • 【社区活动】本社区受官方长期扶持,您可以通过活动打造个人IP,让更多的人受益于您的分享,同时我们还会奉上精美周边;
  • 【赢在CSDN】社区会对社区成员开设博主扶持计划,集结优质博主分享成长经验,更是疑问在线解答,定期直播连麦,只要您是本社区成员皆可免费享受此权益,让我们携手共进助您速获万粉头衔;
  • 【Vue技能树】本社区创建人同时作为Vue技能树构建者,可为本社区开设技能树投稿通道,获得此权益后我们的高质量的文章被技能树收录获得更多曝光机会;
  • 【简历/就业指导】本社区创建者目前兼职高校就业指导,如果您是学生准备找工作或者您是职场人在应聘中遇到任何问题都可以在这里寻求帮助,我们会定期开设简历审查、面试技巧等就业方面的直播讲解;
  • 【技术交流】任何语言任何方向的技术文章我们都可以汇聚于此,大家可以摸鱼时间可以来此处提升自己,遨游在技术的海洋中;
  • 【立码吐槽】不管你是学生还是打工人,相信在生活中肯定有不断的新鲜事发生,这些事情可以是令你高兴的(比如今天过生日)可以是伤心的(比如我们丢了一个发卡)当然还会有很多,不满、发泄、求安慰等等,那么你可以在这个专栏中做出分享,求一句生日快乐、上岸顺利、加油老铁等等暖心的话;相信我们社区的伙伴看到后一定会速来吐槽;
  • 【bug记录】开发中的坑、学习中的雷,我们皆可投递于此,让更多的人借着分享精准避免从而高效开发;
  • 【更多】更多专栏正在筹备中。。。如果您是社区成员、如果您想为社区建设贡献力量,可以私聊社区管理员;

试试用AI创作助手写篇文章吧