webpack 快速入门 系列 —— 实战一
实战一
准备本篇的环境
虽然可以仅展示核心代码,但笔者认为在一个完整的环境中边看边做,举一反三,效果更佳。
这里的环境其实就是初步认识 webpack一文完整的示例,包含 webpack、devServer、处理css、生成 html。
项目结构如下:
webpack-example2 - src // 项目源码 - a.css - b.js - c.js - index.html // 页面模板 - index.js // 入口 - package.json // 存放了项目依赖的包 - webpack.config.js // webpack配置文件
src中的代码如下:
// a.css body{color:blue;} // b.js import './c.js' console.log('moduleB') console.log('b2') // c.js console.log('moduleC') // index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=`, initial-scale=1.0"> <title>Document</title> </head> <body> <p>请查看控制台</p> </body> </html> // index.js import './b.js' import './a.css' console.log('moduleA')
package.json:
{ "name": "webpack-example2", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack", "dev": "webpack-dev-server" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "css-loader": "^5.2.4", "html-webpack-plugin": "^4.5.2", "style-loader": "^2.0.0", "webpack": "^4.46.0", "webpack-cli": "^3.3.12", "webpack-dev-server": "^3.11.2" } }
webpack.config.js:
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry: './src/index.js', output: { filename: 'main.js', path: path.resolve(__dirname, 'dist') }, module: { rules: [ { test: /\.css$/i, use: ["style-loader", "css-loader"] }, ] }, plugins: [ new HtmlWebpackPlugin({ template: 'src/index.html' }) ], mode: 'development', devServer: { open: true, contentBase: path.join(__dirname, 'dist'), compress: true, port: 9000, }, };
在 webpack-example2 目录下运行项目:
// 安装项目依赖的包 > npm i // 启动服务 > npm run dev
启动服务器后,浏览器会自动打开页面,如果看到蓝色文字”请查看控制台“,说明环境已准备就绪。
打包样式
处理 css 和 less
less 是一种 css 预处理语言,在 webpack 中要处理 less 需要使用
less-loader,用于将 less 转为 css。
首先安装依赖,然后修改配置文件:
// 安装包。版本8安装失败,所以降了一个版本 > npm i -D less-loader@7 // webpack. 56c config.js // 增加对 less 文件处理的loader rules: [ // 需要保留,否则识别不了 css 文件 { test: /\.css$/i, use: ["style-loader", "css-loader"] }, // + { test: /\.less$/i, loader: [ // compiles Less to CSS "style-loader", "css-loader", "less-loader", ], }, ],
然后增加 a.less 文件,在 index.js 中引入 a.less,重新启动服务进行测试:
// src/a.less body{ p{ color:pink; } } // index.js import './b.js' import './a.css' // + import './a.less' console.log('moduleA') // 启动服务 > npm run dev
在新开的页面中,看到粉色文字”请查看控制台“,说明 less 处理成功。
提取 css 成单独文件
通过浏览器我们发现现在 css 是嵌在页面内的,就像这样:
<head> ... <style>body{color:blue;}</style> <style>body p { color: pink; } </style> </head>
通常我们会通过 link 来引入 css 文件,所以接下来就将 css 取成单独的文件。这里需要使用
mini-css-extract-plugin这个包。
我们只需要 ad8 安装依赖包,修改配置文件即可:
// 安装依赖包 > npm i -D mini-css-extract-plugin@1 // 不在需要 style-loader,卸载 > npm r -D style-loader // webpack.config.js // + const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { ... module: { rules: [ // 修改规则 { test: /\.css$/i, // 将 style-loader 改为 MiniCssExtractPlugin.loader use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.less$/i, loader: [ // 将 style-loader 改为 MiniCssExtractPlugin.loader MiniCssExtractPlugin.loader, "css-loader", "less-loader", ], }, ], }, plugins: [ // + new MiniCssExtractPlugin(), ... ], };
启动服务(
npm run dev),在打开的页面中可以看到 css 已经改为 link 的方式引入,就是这样:
// 从 style 改为 link 方式 <link href="main.css" rel="stylesheet"> // 通过网络查看 main.css 的内容是: body{color:blue;} body p { color: pink; }
由于我们对 css 和 less 都使用了 MiniCssExtractPlugin.loader,所以 a.css 和 a.less 都被提取到 main.css 中。
Tip:如果通过
npm run build打包,则可以看到 dist/main.css 文件。
使用 PostCSS
PostCSS - 使用JavaScript转换CSS的工具。
可以将 postcss 当作一个平台,下面我们通过 postcss 做两件事:
- 增加代码可读性(或增加前缀)
:fullscreen { } // 转为 :-webkit-full-screen { } :-ms-fullscreen { } :fullscreen { }
- 立即使用明天的CSS
body { color: lch(53 105 40); } // 转为 body { color: rgb(250, 0, 4); }
webpack 可以通过
postcss-loader来使用 postcss。
由于 postcss 只是一个平台,具体功能需要通过插件来实现,这里我们使用
postcss-preset-env。
postcss-preset-env 可以将现代CSS转换为大多数浏览器可以理解的内容,并根据目标浏览器或运行时环境确定所需的polyfill。而且它包含自动前缀。
首先安装相关依赖,并修改配置文件:
> npm i -D postcss-loader@4 postcss-preset-env@6 // webpack.config.js // + const postcssPresetEnv = require('postcss-preset-env'); // + const postcssLoader 564 = { loader: 'postcss-loader', options: { // postcss 只是个平台,具体功能需要使用插件 // Set PostCSS options and plugins postcssOptions:{ plugins:[ // 配置插件 postcss-preset-env [ "postcss-preset-env", { // 自动前缀。默认是true // autoprefixer: true, // 根据您所支持的浏览器来确定需要哪些polyfill。这里仅做演示 browsers: 'ie >= 8, chrome > 10', // stage 默认是 2 // stage:2 }, ], ] } } } module.exports = { module: { rules: [ { test: /\.css$/i, use: [ MiniCssExtractPlugin.loader, "css-loader", // + 放在css-loader后面 postcssLoader ] }, { test: /\.less$/i, loader: [ MiniCssExtractPlugin.loader, "css-loader", // + postcssLoader, "less-loader", ], }, ] }, };
接着修改 a.css 和 a.less,重新启动服务器:
// a.css body{ color: lch(53 105 40); } // a.less body{ p{ transform: scale(1, 2); } } // 启动服务 > npm run dev
在新开的页面中,我们 56c 看到红色文字”请查看控制台“,而且文字纵向拉长了一倍。通过浏览器查看 main.css 源码如下:
body{ color: rgb(250, 0, 4); } body p { -webkit-transform: scale(1, 2); -ms-transform: scale(1, 2); transform: scale(1, 2); }
至此,增加前缀以及立即使用明天的CSS都已经完成。
Tip:stage(阶段)可以是0(实验)到4(稳定),默认是2,如果我们改为3或4,重新打包,
lch(53 105 40);则不会转为
rgb(250, 0, 4);将plugins换成下面的写法效果相同。
plugins:[ postcssPresetEnv({ browsers: 'ie >= 8, chrome > 10', }) ]
postcss-preset-env 支持任何标准的 browserslist 配置,可以是 .browserslistrc 文件,package.json 中的browserslist 键或 browserslist 环境变量。
如果将
browsers: 'ie >= 8, chrome > 10',注释,browsers 将使用默认的 browserslist 查询(即
> 0.5%, last 2 versions, Firefox ESR, not dead),重新构建,则不会添加前缀。
如果不想在 browsers 中写,在 package.json 中的 browserslist 中配置也是可以的:
// package.json { ... "bro ad8 wserslist": [ "ie >= 8", "chrome > 10" ] }
注:package.json 不能写注释,本文在 package.json 中的注释仅作说明。
如果觉得 package.json 写的内容太多,我们甚至可以将这部分提取到一个单独的文件中来写:
// .browserslistrc // from github browserslist # Browsers that we support ie >= 8 chrome > 10
最后,如果我们针对开发环境和生成环境做不同的处理,比如开发环境支持 ie8+,而生产环境支持 chrom10+,我们可以这么写:
// .browserslistrc # Browsers that we support [production] chrome > 10 [development] ie >= 8
然后在配置文件中通过 process.env 来指定环境:
// + // process.env属性返回一个包含用户环境的对象 process.env.NODE_ENV = 'development' // or production
Browserslist将根据BROWSERSLIST_ENV或NODE_ENV变量选择,所以设置
process.env.BROWSERSLIST_ENV也是可以的。
再次打包
npm run build,则只会针对 ie,生成的 main.css 内容如下:
body{ color: rgb(250, 0, 4); } body p { -ms-transform: scale(1, 2); transform: scale(1, 2); }
压缩 css
如果我们需要压缩 css 代码,可以使用
optimize-css-assets-webpack-plugin,用于优化或最小化 css。
首先安装依赖,然后修改配置:
> npm i -D optimize-css-assets-webpack-plugin@5 // webpack.config.js // + const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); plugins: [ new HtmlWebpackPlugin({ template: 'src/index.html' }), new MiniCssExtractPlugin(), // + new OptimizeCssAssetsPlugin() ],
重新打包,原来的 main.css 则变成一行,请看:
> npm run build // main.css(优化前) body{ /* 注释 */ color: rgb(250, 0, 4); } body p { -ms-transform: scale(1, 2); transform: scale(1, 2); } // main.css(优化后) body{color:#fa0004}body p{-ms-transform:scaleY(2);transform:scaleY(2)}
优化后,css 变成了一行,注释也删除了。
打包图片
前端资源通常有图片,由于 webpack 只识别 javascript,所以需要 loader 来帮们识别图片。
我们使用
url-loader,能将图片转为 base64。
首先安装依赖,并修改配置文件:
> npm i -D url-loader@4 // webpack.config.js module: { rules: [ ... // + { test: /\.(png|jpg|gif)$/i, use: [ { loader: 'url-loader', options: { // 指定文件的最大大小(以字节为单位) limit: 1024*7, }, }, ], }, ] },接着引入图片,启动服务:
// 引入图片。src/6.68kb.png // a.less body{ p{ transform: scale(1, 2); } .m-box{display:block;width:100px;height:100px;} .img-from-less{background:url(./6.68kb.png) no-repeat;background-size:100% 100%;} } // index.html ... <body> <p>请查看控制台</p> <span class='m-box img-from-less'></span> </body> ... // 启动服务 > npm run dev
Tip: 笔者的图片大小为 6.68kb,上面的 limit 只需要大于6.68kb即可
在新开的页面中,我们在”请查看控制台“文字下面看见了我们设置的图片。通过检查元素会发现这张图片是 base64。
body .img-from-less{ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA... no-repeat; ... }
如果将
limit: 1024*7修改为
limit: 1024*6(也就是将 limit 设置的比图片的 size 更小),再次运行
npm run dev,会发现报错了。还会提示找不到 file-loader。这是因为这张图片(6.68kb.png)大于 1024*6,所以就不会被打包成 base64,所以需要 file-loader 来处理。
安装依赖包
npm i -D file-loader@6,再次启动服务器,页面上又看到我们的图片,而且这次不再是 base64,而是直接生成了一张图片。
body .img-from-less{ background: url(26bd867dd65e26dbc77d1e151ffd36e0.png) no-repeat; ... }
图片除了在 css 中使用,我们也会通过 img 元素引用,于是我们在 index.html 中新增
<img class='m-box' src="./6.68kb.png" alt="">再次启动服务,在打开的浏览器页面中发现 img 引用的图片没生效,而且源码也没变化。
这里需要使用 html-loader 这个包,它能让每个被加载的属性(例如:
<img src="image.png")能被引入(imported)。
安装依赖包,修改配置:
> npm i -D html-loader@1 // webpack.config.js module: { rules: [ ... // + { test: /\.html$/i, loader: 'html-loader', }, ] },
再次启动服务,就能看到两张一样的图片了。img 的代码变为
<img class="m-box" src="26bd867dd65e26dbc77d1e151ffd36e0.png" alt="">。
如果再次将
limit: 1024*6修改为
limit: 1024*7,启动服务你会发现这两处图片都变为 base64。
打包 javascript
js 语法检查
有时我们希望团队成员写的 javascript 代码风格一致。
我们可以使用 eslint,它能查找并修复JavaScript代码中的问题;可以自定义 eslint,使其完全按照项目所需的方式工作。代码风格,笔者选用 airbnb,一个流行的 javascript 风格指南(此刻是第 6 名(topics javascript))。
在 webpack 中使用 eslint,需要使用
eslint-webpack-plugin( eslint-loader废弃了),而 eslint-webpack-plugin 依赖于
eslint;
eslint-config-airbnb 默认导出包含我们所有的ESLint规则,包括ECMAScript 6+和React,而 我们不需要使用 react,所以使用
eslint-config-airbnb-base即可。
首先安装依赖包,修改配置:
// 没有引入 eslint-plugin-import > npm ad8 i -D eslint@7 eslint-webpack-plugin@2 eslint-config-airbnb-base@14 // webpack.config.js // + const ESLintPlugin = require('eslint-webpack-plugin'); module.exports = { // ... plugins: [ new ESLintPlugin({ // 将启用ESLint自动修复功能。此选项将更改源文件 fix: true }) ], // ... }; // package.json { // + "eslintConfig": { "extends": "airbnb-base" } }
重新打包
npm run build,出现了一些警告和错误,核心信息如下:
WARNING in webpack-example2\src\index.js 6:1 warning Unexpected console statement no-console ✖ 6 problems (2 errors, 4 warnings) ERROR in webpack-example2\src\index.js 1:8 error Unexpected use of file extension "js" for "./b.js" import/extensions ✖ 5 problems (2 errors, 3 warnings)
错误(import/extensions)是不希望使用 js 扩展名,将
./b.js改为
./b就好了,可参考issues:import/extensions。
警告(no-console)是因为不能出现 console.log。可以通过配置将这个告警关闭:
// package.json { "eslintConfig": { "extends": "airbnb-base", // + "rules": { "no-console": "off", } } }
将
import/extensions修复,并将警告关闭,重新打包
npm run build则不会出现警告和错误。
Tip:打包后,源码也会自动修复,比如 src/index.js 中的 sum() 方法,
a,后面是多个空格,打包后会合并成一个空格:
function sum(a, b) { return a + b; } // 需要调用 sum() 方法 // 否则报错:error 'sum' is defined but never used no-unused-vars console.log(sum(1, 100)); // 修复后 function sum(a, b) { return a + b; }
如果在 js 文件中使用 window ,再次打包会报错,就像这样:
// index.js // + setTimeout(() => { window.location = 'https://www.baidu.com/'; }, 1000); // 打包 > npm run build ... error 'window' is not defined no-undef
可以在配置文件中指定环境来解决这个问题。就像这样:
// package.json "eslintConfig": { // + "env": { "browser": true } }
如果不想写到 package.json,也可以配置到单独的文件(.eslintrc.js)中:
// .eslintrc.js module.exports = { "exte ad8 nds": "airbnb-base", "rules": { "no-console": "off" }, "env": { "browser": true } }
js 兼容性处理
我们想使用 es6 来编写代码,但有的浏览器支持的不够全面,所以我们会将 es6 转成 es5。
接着上面的例子进行,重写 index.js,放入一个箭头函数,再次打包,你会发现 webpack 不会对 es6 语法做处理,const 还是 const,而不是 var:
// src/index.js const sum = (a, b) => (a + b); console.log(sum(1, 10)); // sum 还是我们的箭头函数 eval("const sum = (a, b) => (a + b);\nconsole.log(sum(1, 10));\n\n\n//# sourceURL=webpack:///./src/index.js?");
Babel 是一个 JavaScript 编译器。通过它可以让我们使用下一代的 JavaScript 语法编程。
在 webpack 中要使用 babel 就得用
label-loader。用法(Usage)如下:
// 安装依赖包。没有使用 @babel/core > npm i -D babel-loader@8 @babel/preset-env@7 // webpack.config.js module: { rules: [ // + { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: [ ['@babel/preset-env'] ] } } } ] }
重新打包,箭头函数就变成普通函数:
eval("var sum = function sum(a, b) {\n return a + b;\n};\n\nconsole.log(sum(1, 10));\n\n//# sourceURL=webpack:///./src/index.js?");
如果我们在 js 中使用 Promise,重新打包后 Promise 还是 Promise,而且在不识别 Promise 语法的浏览器中(比如 ie11)运行会报错。
// index.js Promise.resolve('aaron').then((v) => { console.log(v); }); // 重新打包,Promise 还是 Promise eval("Promise.resolve('aaron').then(function (v) {\n console.log(v);\n});\n\n//# sourceURL=webpack:///./src/index.js?");
babel 官网提到,使用@babel/polyfill,就可以使用新的内置函数(例如Promise或WeakMap),静态方法(例如Array.from或Object.assign),实例方法(例如Array.prototype.includes)等等。所以这个 polyfill 是我们的解决方案。
但是 @babel/polyfill 废弃了。而 @babel/polyfill 包含 regenerator runtime 和 core-js。
core-js,包括适用于2021年前ECMAScript的polyfill,而且仅加载必需的功能。
在 useBuiltIns 参数中也提到:由于在7.4.0中已 ad8 弃用@ babel/polyfill,因此我们建议直接添加core-js并通过corejs选项设置版本。
于是我们知道 core-js 能解决 Promise 这类问题。
如何使用 core-js ?我们先来介绍一下插件和预设。
babel 通过将插件(或预设)应用于配置文件来启用Babel的代码转换。比如插件列表中的
es2015,这是一个集合,包含了箭头函数(arrow-functions)、类(classes)等插件;
而预设(presets)其实是多个插件(plugin)的集合。比如 @babel/preset-env 这种预设则包含了 es2015、es2016、es2017等最新的插件。
最后根据babel-preset-env中的介绍,我们将 core-js 应用上:
// 安装依赖 > npm i -D core-js@3.11 // webpack.config.js { loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', // + { // 配置处理polyfill的方式 useBuiltIns: "usage", // 版本与我们下载的版本保持一致 corejs: { version: "3.11"}, "targets": "> 0.25%, not dead" } ] ] } }
重新打包,dist/main.js 的Size变成 109 Kib,而之前还不到 4kiB。
启动服务,在不支持 Promise 语法的浏览器中,比如 ie11,也能在控制台输入 aaron。
至此 javascript 的兼容处理完毕。
- webpack快速入门和实战
- webpack从入门到实战(从V1到V2版本迁移)-余鹏-专题视频课程
- 快速入门webpack模块打包器
- Docker系列一之基础快速入门企业实战
- webpack 学习笔记 02 快速入门
- Vue.js——60分钟webpack项目模板快速入门
- vue最简单的入门教程+实战+Vue2+VueRouter2+webpack(二)
- Webpack入门之遇到的那些坑,系列示例Demo
- webpack4.x零基础,入门到实战
- Vue.js——60分钟webpack项目模板快速入门
- Vue.js——60分钟webpack项目模板快速入门
- Docker系列一之基础快速入门企业实战
- 最新Webpack4.0从入门到高手实战开发
- 【Java Web开发系列课程】JDBC数据库开发快速入门
- vue最简单的入门教程+实战+Vue2+VueRouter2+webpack(三)
- 【webpack系列】入门(系统性了解webpack)
- ASP.NET Web 应用开发实战快速上手系列 2——C#基础
- Vue.js——60分钟webpack项目模板快速入门
- Vue.js——60分钟webpack项目模板快速入门
- Spring Boot 2.0 WebFlux 上手系列课程:快速入门(一)