Webpack5 学习 - 基础篇

Webpack5 学习 - 基础篇

node v16.13.0

npm v8.1.4

react v17

webpack v5 - 官网

相关概念

  • Entry:编译入口,webpack 编译的起点
  • Compiler:编译管理器,webpack 启动后会创建 compiler 对象,该对象一直存活知道结束退出
  • Compilation:单次编辑过程的管理器,比如 watch = true 时,运行过程中只有一个 compiler 但每次文件变更触发重新编译时,都会创建一个新的 compilation 对象
  • Dependence:依赖对象,webpack 基于该类型记录模块间依赖关系
  • Module:webpack 内部所有资源都会以“module”对象形式存在,所有关于资源的操作、转译、合并都是以 “module” 为基本单位进行的
  • Chunk:编译完成准备输出时,webpack 会将 module 按特定的规则组织成一个一个的 chunk,这些 chunk 某种程度上跟最终输出一一对应
  • Loader:资源内容转换器,其实就是实现从内容 A 转换 B 的转换器
  • Plugin:webpack构建过程中,会在特定的时机广播对应的事件,插件监听这些事件,在特定时间点介入编译过程

安装环境

$ mkdir webpack-demo
$ cd webpack-demo
$ npm init -y
$ npm i react react-dom -S
$ npm i webpack webpack-cli -D
$ npm i webpack-merge -D // webpack-marge合并通用配置和特定环境配置

//安装完成
//"react": "^17.0.2",
//"react-dom": "^17.0.2"
//"webpack": "^5.65.0",
//"webpack-cli": "^4.9.1",
复制代码

创建以下目录结构、文件和内容:

webpack-demo
  |- package.json
  |- package-lock.json
+ |- /src
+   |- hello.js
+   |- index.js
+ |- /config
+   |- webpack.common.js //通用环境配置文件
+   |- webpack.dev.js    //开发环境配置文件
+   |- webpack.prod.js   //生产环境配置文件
复制代码

hello.js:

function component() {
    const element = document.createElement('div');
    element.innerHTML = '【hello.js】Hellp webpack';
    return element;
}
document.body.appendChild(component());
复制代码

index.js:

import './hello.js'
function component() {
    const element = document.createElement('div');
    element.innerHTML = '【index.js】哈哈';
    return element;
}
document.body.appendChild(component());
复制代码

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, {}) // 暂不添加配置
复制代码

package.json:

{
  "name": "webpack-demo",
  ...
- "main": "index.js",
+ "private": true,
  ...
}
复制代码

模式(mode)

修改webpack.dev.js

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

修改webpack.prod.js

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

entry 和 output

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

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

修改 webpack.commom.js

const path = require('path')
module.exports = {
    entry: {  // 入口
        index: './src/index.js',
    },
    output: {  //输出
        filename: '[name].bundle.js', // bundle 文件名称
        path: path.resolve(__dirname, '../dist'), // bundle 文件路径
        clean: true // 编译前清除目录
    },
} 
复制代码

执行:

$ npx webpack --config config/webpack.dev.js
复制代码

可以在命令行中看到打包的结果,并且在根目录下生成了一个dist目录及输出文件

html-webpack-plugin

从模板生成一个HTML文件

$ npm i html-webpack-plugin -D //安装
复制代码

新建 src/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

复制代码

修改 webpack.commom.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    //...
    plugins: [
        // 生成html,自动引入所有bundle
        new HtmlWebpackPlugin({
            title: 'webpack-demo',
            template: path.resolve(__dirname, '../src/index.html'),
            filename: 'index.html',
        }),
    ],
    //...
} 
复制代码

执行:

$ npx webpack --config config/webpack.dev.js
复制代码

编译后新生成了 index.html,自动插入了标题和script,如下图:

webpack-dev-server 自动编译

webpack-dev-server 提供了一个基本的 web server,并且具有实时重新加载功能,帮助我们在代码发生变化后自动编译代码。

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

$ npm i webpack-dev-server -D //安装
复制代码

修改 webpack.dev.js

const { merge } = require('webpack-merge') 
const common = require('./webpack.common') 
const path = require('path')
module.exports =  merge(common, {
    mode: 'development', // 开发模式
    devServer: {
        static: {
            directory: path.join(__dirname, '../dist'),
        },
    },
})
复制代码

执行:

$ npx webpack serve --open --config config/webpack.dev.js
复制代码

修改一下js,可以马上看到页面对应变化

cross-env 优化编译指令

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

$ npm i cross-env -D
复制代码

修改 package.json:

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

现在可以运行 webpack 指令:

  • npm run start:本地构建开启自动编译;
  • npm run dev:本地构建;
  • npm run build:生产打包;

loader

webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。

加载 CSS/Sass/SCSS

安装 style-loader 、css-loadersass-loader ,并在 module 配置 中添加这些 loader:

$ npm i style-loader css-loader -D
$ npm i sass-loader sass -D
复制代码

修改 webpack.common.js

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

新建 src/style.css:

.hello {
    color: red;
}
复制代码

新建 src/style.scss:

$body-color: orange;
body {
  color: $body-color;
}
复制代码

修改 src/hello.js:

import './style.css';
function component() {
    const element = document.createElement('div');
    element.innerHTML = '【hello.js】Hellp webpack';
    element.classList.add('hello'); //add
    return element;
}
document.body.appendChild(component());
复制代码

修改 src/index.js:

import './hello.js'
import './style.scss'; //add
function component() {
    const element = document.createElement('div');
    element.innerHTML = '【index.js】哈哈,修改一下';
    return element;
}
document.body.appendChild(component());
复制代码

重新编译,查看结果:

postcss-loader

postcss-loader : 使用 PostCSS 处理 CSS 的 loader

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

  • 可以自动为 CSS 规则添加前缀;
  • 将最新的 CSS 语法转换成大多数浏览器都能理解的语法;
  • css-modules 解决全局命名冲突问题。

修改样式 style.scss

$body-color: orange;
body {
  color: $body-color;
  user-select:none; //add
}
复制代码

$ npm i postcss-loader postcss postcss-preset-env -D //安装
复制代码

配置:

module.exports = {
    module: {
        rules: [
            {
                test: /.(css|scss|sass)$/,
                use: [
                    'style-loader', // 将 JS 字符串生成为 style 节点
                    {
                        loader: "css-loader",// 将 CSS 转化成 CommonJS 模块
                        options: { importLoaders: 1 },
                      },
                    {
                        loader: 'postcss-loader', // 将 PostCSS 编译成 CSS
                        options: {
                            postcssOptions: {
                                plugins: [
                                    [
                                        'postcss-preset-env', // postcss-preset-env 包含 autoprefixer
                                    ],
                                ],
                            },
                        },
                    },
                    'sass-loader',  // 将 Sass 编译成 CSS
                ],
            },
        ],
    },
} 
复制代码

重新编译,查看结果:

分离样式文件

通过 CSS 文件的形式引入到页面

$ npm i mini-css-extract-plugin -D // 安装
复制代码

修改

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
    plugins: [
        new MiniCssExtractPlugin({ 
            filename: '[name].[hash:8].css'
        }),
    ],
    module: {
        rules: [
            {
                test: /\.(s[ac]|c)ss$/i, //匹配所有的 sass/scss/css 文件
                use: [
                    // 'style-loader', // 将 JS 字符串生成为 style 节点
                    MiniCssExtractPlugin.loader, // 添加 loader
                    {
                        loader: "css-loader",// 将 CSS 转化成 CommonJS 模块
                        options: { importLoaders: 1 },
                      },
                    {
                        loader: 'postcss-loader', // 将 PostCSS 编译成 CSS
                        options: {
                            postcssOptions: {
                                plugins: [
                                    [
                                        'postcss-preset-env', // postcss-preset-env 包含 autoprefixer
                                    ],
                                ],
                            },
                        },
                    },
                    'sass-loader',  // 将 Sass 编译成 CSS
                ],
            },
        ],
    },
} 
复制代码

重新编译,查看结果:

加载图像和字体(资源模块)

webpack5 新增资源模块(Asset Modules),允许使用资源文件(字体,图标等)而无需配置额外的 loader

  • asset/resource:将资源分割为单独的文件,并导出 url,类似之前的 file-loader 的功能.
  • asset/inline:将资源导出为 dataUrl 的形式,类似之前的 url-loader 的小于 limit 参数时功能.
  • asset/source:将资源导出为源码(source code). 类似的 raw-loader 功能.
  • asset:会根据文件大小来选择使用哪种类型,当文件小于 8 KB(默认) 的时候会使用 asset/inline,否则会使用 asset/resource

加载 images 图像加载 fonts 字体

修改 webpack.common.js

module.exports = {
    module: {
        rules: [
            {
                test: /.(css|scss|sass)$/,
                use: [
                    'style-loader', // 将 JS 字符串生成为 style 节点
                    'css-loader',   // 将 CSS 转化成 CommonJS 模块
                    'sass-loader',  // 将 Sass 编译成 CSS
                ],
            },
            //加载图片
            {
                test: /\.(png|svg|jpg|jpeg|gif)$/i,
                type: 'asset/resource',
            },
            //加载字体
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/i,
                type: 'asset/resource',
            },
        ],
    },
}  
复制代码

添加图片 src/icon.png,修改src/index.js:

import './hello.js'
import './style.scss';
import Icon from './icon.png'
function component() {
    const element = document.createElement('div');
    element.innerHTML = '【index.js】哈哈,修改一下';

    // 将图像添加到我们已经存在的 div 中。
    const myIcon = new Image();
    myIcon.src = Icon;
    element.appendChild(myIcon);

    return element;
}
document.body.appendChild(component());
复制代码

修改 style.scss

.hello {
    color: red;
    background: url('./icon.png');
}
复制代码

重新编译,查看结果:

Babel(JS 兼容性)

安装依赖

  • babel-loader:使用 Babel 加载 ES2015+ 代码并将其转换为 ES5
  • @babel/core:Babel 编译的核心包
  • @babel/preset-env:Babel 编译的预设,可以理解为 Babel 插件的超集
  • @babel/preset-react:Babel 转译 jsx语法
$ npm i babel-loader @babel/core @babel/preset-env @babel/preset-react -D
复制代码

修改 index.js:

import './hello.js'
import './style.scss';
import icon from './icon.png'
import React from "react";
import ReactDOM from 'react-dom';
const name = 'IT姑凉';
const element = <div>
    <h1>Hello, {name}</h1>
    <img src={icon} />
</div>;

ReactDOM.render(
  element,
  document.getElementById('root')
);
复制代码

这个时候编译是报错的

修改 webpack.common.js

module.exports = {
    module: {
        rules: [
            //babel
            {
                test: /\.js$/,
                exclude: /(node_modules|bower_components)/, //不转换的文件
                use: {
                  loader: 'babel-loader',
                  options: {
                    presets: ['@babel/preset-react','@babel/preset-env']
                  }
                }
            }
        ],
    },
} 
复制代码

重新编译,查看结果:

devtool Source Map

我们在开发过程中免不了需要去调试代码,这时候webpack的devtool选项就十分重要了,devtool 有许多 可用选项

常用devtool配置选项:

  • source-map :产生一个单独的source-map文件,功能最完全,但会减慢打包速度
  • eval-source-map :使用eval打包源文件模块,直接在源文件中写入干净完整的source-map,不影响构建速度,但影响执行速度和安全,建议开发环境中使用,生产阶段不要使用
  • cheap-module-source-map:会产生一个不带映射到列的单独的map文件,开发者工具就只能看到行,但无法对应到具体的列(符号),对调试不便
  • cheap-module-eval-source-map:不会产生单独的map文件,(与eval-source-map类似)但开发者工具就只能看到行,但无法对应到具体的列(符号),对调试不便

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

修改 webpack.dev.js

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

分类:
前端
标签: