webpack 和 rollup 使用比较 —— 从 0 搭建 web 项目
webpack 和 rollup 都有好些年的历史了,一般在 web 项目中使用 webpack,而库的开发则是使用 rollup 较多。
其实现在很多简单的功能,两者都可以使用。今天我们就来使用这两个工具,来创建一个从零开始的 web 项目。
基本结构
- 创建一个文件夹作为项目的根文件夹
- 使用
npm init -y
创建 package.json - 使用
npm i rollup webpack webpack-cli cross-env -D
安装基本的库 - 创建 src 文件夹作为代码文件,dist-webpack 和 dist-rollup 两个文件夹存放分别构建后的代码
- 创建 index-webpack.html 和 index-rollup.html 作为 html 入口;创建 webpack.config.js 和 rollup.config.js 作为构建配置文件
- package.json
scripts
字段增加"build": "webpack; rollup --config"
命令
第一步:输入和输出
我们在 src 目录下创建 a.js 和 index.js
// a.js
export default 'hello word'
// index.js
import a from './a';
console.log(a)
webpack
编写 webpack.config.js,这个配置文件只能用 cjs 的方式编写。
mode
是 webpack4 为了简化配置推出的一种预设,默认是 production
。为了让构建后的产物有可读性,我们把它设置为 development
,再把 devtool
设置为 false
。;当然,也可以直接设置为 none
。
module.exports = {
mode: 'development',
devtool: false,
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist-webpack'),
filename: '[name].js'
}
}
rollup
编写 rollup.config.js,这个配置文件可以用 esm 或 cjs 两种方式编写。
input
代表入口文件。 output
中 file
代表输出的文件可以写相对路径;format
表示输出的代码形式。
module.exports {
input: 'src/index.js',
output: {
file: 'dist-rollup/main.js',
format: 'iife', // 立即执行函数
},
}
查看结果
运行 npm run build
生成结果。
然后我们给两个 html 添加 <script>
引用构建后的 js,在根目录使用 http-server 启动静态资源服务器,就可以访问两个 html 查看控制台输出。两者都正常输出了 hello world
。
第二步:使用第三方库
我们将要使用 lodash
和 jquery
这两个第三方库,首先安装它们:
$ npm i lodash jquery -S
在 index.js 中增加第三方库的使用:
import _ from 'lodash'
import $ from 'jquery'
console.log(a)
console.log(_.range(10))
我们可以把他们打入到 bundle 中一起使用,也可以通过 <script>
直接引入放在 cdn 或自己服务器上的资源。
方式一:把第三方库打入 bundle 中
webpack
webpack 直接就会把他们打包到 bundle 中。从构建产物中查看 __webpack_modules__
的值,包含了这两个库和 ./src/a.js
这个自己的模块。
rollup
而 rollup 则要复杂一些。它有两个特点:
- 第一是不会把 node_modules 里面的内容打包进来,所以需要使用 @rollup/plugin-node-resolve 插件
- 第二是默认只支持 esm 的模块,而 lodash 和 jquery 的源码并未提供 esm 的支持(如果第三方库有 package.json 有 module 字段,rollup 和 webpack 这样的 es6 感知工具会直接导入 es6 的版本)。所以需要使用 @rollup/plugin-commonjs 支持 cjs 的模块。
安装这俩插件:
$ npm i @rollup/plugin-node-resolve @rollup/plugin-commonjs -D
然后在 rollup.config.js 中添加配置:
const nodeResolve = require('@rollup/plugin-node-resolve');
const commonjs = require('@rollup/plugin-commonjs');
module.exports.plugins = [
nodeResolve(),
commonjs(),
]
可以看到构建产物中,这两第三方模块的内容都打入了 bundle 中。
查看结果
刷新两个页面,都可以看到页面出现了 123 标题;控制台出现一个长度为 10 的数组。
方式二:将第三方库作为外部依赖
这是种什么方式?
将第三方库作为外部依赖,放在 cdn 上。一方面不同项目和页面都可以复用,另一方面构建速度会变快,打包之后的包体积都会变小。
首先,我们通过 <script>
标签引入 jquery 和 lodash 这两个库。
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
// 下面才是 <script src="main.js">
我们引入了这两个库,就可以直接通过 window.jQuery
(window.$
)和 window._
对他们进行访问。
然后,我们要在构建配置文件中将这俩库排除。
webpack
module.exports.externals = {
jquery: 'jQuery', // 设为 '$' 也可以
lodash: '_'
}
可以看到构建的代码中,有大概这样的代码,相当于是把全局变量作为这个模块的导出值。
var __webpack_modules__ = {
lodash: module => { module.exports = _; },
jquery: module => { module.exports = jQuery; },
// 其他模块
}
rollup
rollup 需要配置两个地方,output.golbals
告诉 rollup 这些是获取 window
上的属性;external
告诉 rollup 不要打包这些库。
module.exports.output.globals = {
jquery: 'jQuery', // 设为 '$' 也可以
lodash: '_'
}
module.exports.external = ['lodash', 'jquery']
可以看到构建的代码中,有大概这样的代码,把全局变量作为这个 iife 的参数。同时 jquery 和 lodash 的源码也不会打包进去。
(function (_, $) {
// 具体的内容
})(_, jQuery);
查看结果
和第一种方式显示效果相同。
第三步:使用 babel 进行处理
之前,前端开发很麻烦的一点就是兼容性处理。这个在 babel 出现之后得到了好转。现在虽然浏览器兼容性比较好了,但是仍然还是有部分需要转换的,使用 babel 可以让我们使用浏览器还没支持的最新的标准,甚至是草案;另外像 React 的 jsx 也需要转码。
目标:编译 es6 和 jsx 代码
我们为 index.js 添加如下代码:
import React from 'react';
import ReactDOM from 'react-dom';
const root = document.createElement('div')
document.body.appendChild(root)
ReactDOM.render((<h2>456</h2>), root, () => {
console.log('render!')
})
同时用第二步的方式二的形式,添加 cdn 上的 react 和 react-dom 库,注意选择 umd 格式的资源:
<script src="https://cdn.bootcdn.net/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
之后的配置除了要给 externals
或 output.globals
+ external
属性添加这两个库外,最重要的就是加入 babel 相关的配置。
加入 babel 家族的工具们
使用 babel 比较好的实践是把配置放在 babel.config.js 中。我们在根目录创建这个文件:
module.exports = {
presets: [
[ '@babel/env', { modules: false } ],
'@babel/react'
]
}
注意 @babel/env 的配置参数 { modules: false }
表示希望 babel 保留 esm 的方式,不要对模块形式进行转换。其实也可以不设置这个参数,相当于直接用默认值 auto
。它会识别你是在用打包器还是 @babel/cli,从而正确地选择模块的形式。
然后安装:
# 这个是 babel 本身使用需要的,由于是用作插件,所以不需要 @babel/cli
$ npm i @babel/core @babel/preset-env @babel/preset-react -D
# 这个是 webpack 的 loader 和 rollup 的 plugin
$ npm i babel-loader @rollup/plugin-babel -D
然后两个配置工具的配置方式如下:
webpack
webpack 里有两个不同的概念:loader 和 plugin:
- loader 是在
fs
的 api 读取每一个文件之后,经过匹配,要进行的处理的一段管道。 - plugin 是在 webpack 不同生命周期的 hooks 上进行订阅处理器,然后在 hooks 触发时进行额外操作。
其实 loader 做的事情,plugin 也能做。但是 loader 更加有序和清晰。
而 babel 在 webpack 里使用的是 babel-loader
,这个不需要使用 require 引入。
module.exports.module = {
rules: [
{
test: /.js$/,
exclude: /(node_modules|bower_components)/,
use: 'babel-loader'
}
]
}
注意优化所用到的 exclude
参数,表示 node_modules 里的 js 是不需要通过 babel 来转换的。因为我们引用的 node_modules 的库,是已经经过了 babel 编译的。
rollup
rollup 中没有区分 loader 的概念。都是 plugin。
const babel = require('@rollup/plugin-babel')
module.exports.plugins = [
babel({
exclude: 'node_modules/**',
babelHelpers: 'bundled'
})
]
和 webpack 一样,它也有 exclude
参数。
查看结果
刷新,可以看到页面上出现了二级标题 456
第四步:使用 typescript
ts 配置和安装
和 babel 类似,typescript 也是推荐把配置文件放在 tsconfig.json 中
{
"compilerOptions": {
"target": "es5",
"module": "ESNext",
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
安装
# ts 本体
$ npm i typescript tslib -D
# webpack 和 rollup 构建所需
$ npm i ts-loader @rollup/plugin-typescript
然后我们添加一个 b.ts
// b.ts
const b: string = 'b'
export default b
// index.js
import b from './b'
console.log(b)
webpack
webpack 针对 .ts
后缀的文件,增加一个匹配规则。注意 use
的 loaders 的顺序是数组从右往左执行。最右侧的 loader 会读取源文件的内容,而最左侧的 loader 则需要返回合法的 js 代码字符串。
另外 webpack 默认只能省略 ['.js', '.json', '.wasm']
后缀,而我们引入 b.ts
的模块时没有加后缀;我们需要为 extensions
添加额外的后缀。可以采用 '...'
访问默认后缀列表。
module.exports.resolve.extensions = ['.ts', '...']
module.exports.module.rules = [
{
test: /.ts$/,
exclude: /(node_modules|bower_components)/,
use: [
'babel-loader',
'ts-loader',
]
},
// 其他 rule...
]
rollup
rollup 相对而言更加简单:
const typescript = require('@rollup/plugin-typescript')
module.exports.plugins = [
// 其他插件...
typescript()
]
第五步:样式处理
样式处理的目标
样式处理一般包括这些工作:
- 读取 css 文件
- 读取并编译 less, scss, styl 文件
- css module
- postcss 处理
- 将 css 字符串插入 dom 中,或者将这些字符串提出生成 css 文件
- 压缩 css
这里举一个例子,尽可能多地包含相关的内容。
我们使用 less 作为样式文件进行编写
然后创建 c.less,希望它作为全局样式:
@color: grey;
body {
color: @color;
}
创建 d.module.less,希望把使用 css module:
@color: red;
.red {
color: @color;
}
然后通过 index.js 来引用两者:
import './c.less'
import d from './d.module.less'
console.log(d.red)
为了实现这些功能,webpack 需要组合使用一堆独立的 loader 和 plugin。而 rollup 就非常简单,一个 plugin 包含了所有的功能。
方式一:脚本插入 <style>
webpack
安装以下内容:
$ npm i less-loader css-loader style-loader -D
然后添加一条 rule:
module.exports.module.rules = [
{
test: /.less$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
auto: true // 为所有满足 /\.module(s)?\.\w+$/i.test(filename) 条件的文件设置 modules.mode 选项为 local
}
}
},
'less-loader',
]
},
// 其他 rule
]
rollup
使用 rollup-plugin-postcss 插件一站式解决:
$ npm i rollup-plugin-postcss less -D
在配置文件中设置上 module 的配置即可。less 文件不需要额外的配置,只要确保 less 是安装了的,import
less 文件时就会自动识别和处理。
const postcss = require('rollup-plugin-postcss')
module.exports.plugins = [
postcss({
modules: true,
autoModules: /\.module\.\S+$/,
})
// 其他插件...
]
查看结果
打开页面,审查元素,可以看到 <head>
被插入两个 <style>
标签:
index-wepback.html 执行:
index-rollup.html 执行:
然后控制台也会打印出 css module 化的 className
方式二:抽离独立 css 文件
把 css 文件放在 <head>
中,在 <body>
的任何内容出现之前加载,是一直以来的最佳实践。可以避免页面闪动,减少页面重绘。
webpack
过去实现这个功能用的是:extract-text-webpack-plugin。现在,建议使用最新的 mini-css-extract-plugin 插件。
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module.exports.plugins = [ new MiniCssExtractPlugin() ]
另外把 style-loader
替换成 MiniCssExtractPlugin.loader
rollup
只需要给 postcss
插件的调用增加一项配置 extract: true
查看结果
在各自的 dist 目录中,均出现了 main.css 文件。内容就是方式一两个 <style>
的内容。
如果愿意可以配置压缩选项。
总结
webpack 从 v1 到现在的 v5,不断地优化性能,增加功能,简化配置;再加上社区努力地翻译文档,终于让初学者觉得上手轻松了一点。
但这个过程中配置不断地变化,也带来了很多迁移的成本。作者本人从 15 年开始使用 webpack,这中间也需要每年集中学习一遍,毕竟这玩意不是每天都会去修改的东西,不会天天都去研究,也总是记不住配置项,只能总结一个适合自己公司的最佳实践的模版各个项目使用。
现在基于 rollup 新构建工具的 vite 的异军突起,让我们这些对 webpack 又爱又恨的菜鸟喜不自禁,又有新的东西可以学了!
当然从上面的几个步骤可以看出,新的工具在使用上要简单很多。其实,构建工具的使用,更多的是学习“需求”,这个“需求”是咱们前端从我们的工作和任务中自己给自己的 ———— 明白要做什么事情,然后朝着这个目标,比较地学习两个技术,可以学得比较深刻。
转载自:https://juejin.cn/post/7142853148279832584