语
在最近的项目开发中,涉及到了多页面的 webpack 打包,以下是我项目过程中的一些踩坑总结。
前言
项目使用了 vue 作为框架来开发前端页面,其中需要开发多个前端页面,包括有登录、进游戏、充值等等。作为vue最佳的打包工具—— webpack,需要将各个页面分别打包到不同模板目录里。
但默认的 vue 项目框架是单页面应用的,并不能达到项目开发的目的。这就需要调整 webpack 的配置来实现多页面的发布处理。
以下是目录结构:
project
├───bin
│ └───vb.js
├───build
│ │ dev.js
│ │ release.js
│ │ webpack.config.base.js
│ │ webpack.config.build.js
│ └───webpack.config.dev.js
│ README.md
│ package.json
└───src
├───components
│ │ count.vue
│ │ dialog.vue
│ │ errortips.vue
│ └───...
├───game
│ │ game.htm
│ │ game.js
│ └───game.vue
├───login
│ │ login.htm
│ │ login.js
│ └───login.vue
├───pay
│ │ pay_result.htm
│ │ pay_result.js
│ │ pay_result.vue
│ │ pay.htm
│ │ pay.js
│ └───pay.vue
└───...
修改配置前的一些知识
我们知道webpack的核心是一切皆模块,所以它本质上是一个静态模块打包器。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
官网显示的这幅图很形象地描述了这个过程。
从 webpack v4.0.0 开始,webpack 提供了一系列的配置默认项,让开发者可以零配置打包,不再强制要求必须进行繁琐的 webpack 配置,让开发者可以从繁琐的配置文件里抽出,专注应用的开发。但是若你需要有特殊的处理,webpack 仍然可以进行高度可配置来满足你的需求。
在开始前需要了解四个核心概念:
本篇将会针对这4个核心配置的修改和优化来实现多页面打包。在 webpack4 的版本,还新增了一个 mode 配置项。mode 有两个值:development 或者是 production,用户可以启用相应模式下的 webpack 内置的优化。不同 mode 的区别与默认配置可以参考:https://segmentfault.com/a/1190000013712229
一、入口配置
在单页面应用里,一般在根目录下面会有一个 index.html 文件。它是页面的 html 模板文件。但是在多页面应用里,则会有多个应用模板文件,为了方便管理,可以将不同类的入口文件、逻辑处理和模板文件分别存放在相应的独立目录。若用到了组件,则单独将组件存放在一个目录。
project
└───src
├───components
│ │ count.vue
│ │ dialog.vue
│ │ errortips.vue
│ └───...
├───game
│ │ game.htm
│ │ game.js
│ └───game.vue
├───login
│ │ login.htm
│ │ login.js
│ └───login.vue
├───pay
│ │ pay_result.htm
│ │ pay_result.js
│ │ pay_result.vue
│ │ pay.htm
│ │ pay.js
│ └───pay.vue
└───...
webpack 的入口配置中是支持多入口的,给 entry 传入对象即可,如下所示:
const config = {
entry: {
game: './src/game/game.js',
login: './src/login/login.js',
pay: './src/pay/pay.js',
pay_result: './src/pay/pay_result.js'
}
};
但这样的配置对于未知页面数量的项目并不友好,若每新增页面都要重新配置和重启程序,显然是不合理的。而我们可以创建一个getEntry()的方法来遍历文件夹来获取入口。
const fs = require('fs');
const glob = require("glob");
function getEntry() {
const entry = {};
//读取src目录所有page入口
glob.sync('./src/*/*.js') //获取符合正则的文件数组
.forEach(function (filePath) {
var name = filePath.match(/\/src\/(.+)\/*.js/);
name = name[1];
//须有配套的模板文件才认为是入口
if (!fs.existsSync('./src/' + name + '.htm')) {
return;
}
entry[name] = filePath;
});
return entry;
};
module.exports = {
// 多入口
entry: getEntry(),
}
二、输出配置
输出配置仅需指定一个
const config = {
output: {
path: path.join(__projectDir, __setting.distJs),
publicPath: __setting.domainJs, //自定义变量,用来定义公共静态资源路径
filename: '[name][hash].js'
},
};
https://www.webpackjs.com/configuration/output/#output-publicpath
https://www.webpackjs.com/configuration/output/#output-filename
在配置中有以下几点需要注意:
publicPath 是指定在浏览器中所引用的「此输出目录对应的公开 URL」。
简单的例子:
publicPath: "https://cdn.example.com/assets/"
输出到html则变成
<script src="https://cdn.example.com/assets/bundle.js"></script>
这个属性是整个项目共用的静态资源路径,若某个模块需要使用其他的静态资源路径。webpack 提供了__webpack_public_path__来动态设置 publicPath,只需在入口文件的最顶部定义即可。
__webpack_public_path__ = myRuntimePublicPath; // 一定要写在最顶部
filename的[hash]是以项目为维度的 hash 值,若输出了多个文件,则文件名都会共用一个 hash 值。
filename的[chunkhash]是以chunk为维度生成的 hash 值,不同入口生成不同的 chunkhash 值。
filename的[contenthash]根据资源内容生成的 hash 值。
通常使用 hash 或 chunkhash,contenthash 通常用于某些特殊场景(官方文档在使用 ExtractTextWebpackPlugin 插件时有使用)。
https://www.webpackjs.com/plugins/extract-text-webpack-plugin/
三、loader配置
由于 webpack 只能理解 JavaScript 和 JSON 文件。而配置 loader 就是让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块。
loader 可以使开发者在 import 或"加载"模块时预处理文件。例如,将内联图像转换为 data URL,或者允许开发者直接在 JavaScript 模块中 import CSS文件
1、js 模块
加载js模块,我们通常是为了引入babel,让其能将ES6的语法转成ES5,让项目能在低版本的浏览器上运行。
js文件需要使用babel的话,引入babel-loader
const config = {
module: {
rules: [{
test: /\.js$/,
include: [path.resolve(__projectDir, 'src')], //通过include精确指定只处理哪些目录下的文件
exclude: /node_modules/, //设置哪些目录里的文件不进行处理
loader: "babel-loader"
}]
}
}
但仅仅配置了babel-loader还不够,还需要配置 babel 的环境,需要引入 polyfill。
引入 polyfill 的方式有很多种,根据 vue 官方文档在浏览器兼容性的处理,默认使用的是@vue/babel-preset-app ,它通过@babel/preset-env和browserslist配置来决定项目需要的 polyfill。
https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/babel-preset-app
browserslist
项目根目录创建.browserslist文件
> 1%
last 2 versions
当然,你也可以在package.json文件里添加的browserslist字段来配置。
这个配置的目的是为了指定了项目的目标浏览器的范围,配置的值会被 @babel/preset-env 用来确定需要转译的 JavaScript 特性。
详细的说明可以查阅 https://github.com/browserslist/browserslist,了解如何指定浏览器范围。
Polyfill
项目根目录创建.babelrc文件
{
"presets": [
["@babel/preset-env",
{
"modules": false, // 对ES6的模块文件不做转化,以便使用tree shaking、sideEffects等
"useBuiltIns": "entry", // browserslist环境不支持的所有垫片都导入
"corejs": {
"version": 3, // 使用core-js@3
"proposals": true
}
}
]
]
}
这里特别说下的是配置里的useBuiltIns,可设置的值分别是"usage" | "entry" | false,3个值分别代表:
项目使用的是"useBuiltIns": "entry",所以需要指定corejs的版本,这里使用的版本是core-js@3,所以我们在 webpack 的入口配置里加上"core-js/stable"和 "regenerator-runtime/runtime"。
function getEntry() {
const entry = {};
//读取src目录所有page入口
glob.sync('./src/*/*.js') //获取符合正则的文件数组
.forEach(function (filePath) {
var name = filePath.match(/\/src\/(.+)\/*.js/);
name = name[1];
//须有配套的模板文件才认为是入口
if (!fs.existsSync('./src/' + name + '.htm')) {
return;
}
entry[name] = ["core-js/stable", "regenerator-runtime/runtime", path.join(__projectDir, filePath)];
});
return entry;
};
2、css 模块
我们通常使用style-loader和css-loader。css-loader用来处理 js 文件中引入的 css 模块(处理@import和url()),style-loader是将css-loader打包好的css代码以<style>标签的形式插入到 html 文件中。而 webpack 对于 loader 的调用是从右往左的,所以通常是这样配置:
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
}
我们在项目中还经常会使用 sass 或者 scss。sass 是一种 CSS 的预编译语言。因此 webpack 要将其处理会使用更多 loader。
{
test: /\.(sc|sa)ss$/,
use: [{
loader: 'vue-style-loader'
}, {
loader: 'css-loader',
options: {
sourceMap: true,
}
}, {
loader: 'postcss-loader',
options: {
sourceMap: true
}
}, {
loader: 'sass-loader',
options: {
sourceMap: true
}
}, {
loader: 'sass-resources-loader', //组件里面使用全局scss
options: {
sourceMap: true,
resources: [
path.resolve('./src/public/css/common.scss')
]
}
}]
}
在使用sass-loader的时候若某个 scss 文件(比如a.scss)@import 了其他 scss 文件(比如b.scss),如果b.scss里的url()的路径是相对路径,在sass-loader处理过后给css-loader处理时就会报错,找不到url()里指定的资源。
这是因为sass-loader处理时,会将 scss 文件里 @import 路径的文件一并合并进来,结合上面的例子就是b.scss会被sass-loader合并进a.scss。
如何解决呢?可以有两个解决方法:
在项目中由于还用到了postcss-loader,我们还须要在根目录创建postcss-loader的配置文件postcss.config.js
//自动添加css浏览器前缀
module.exports = {
plugins: [
require('autoprefixer')
]
}
3、图片等静态资源
对于图片资源的打包,经常会使用file-loader来完成,配置也很简单:
{
test: /\.(gif|png|jpe?g)$/,
loader: 'file-loader',
}
打包后,会将图片移动到了 dist 目录下,并将该图片改名为[hash].[ext]格式的图片。开发者也可以根据需要,修改输出的文件名。
但在项目开发过程中,我们会创建很多张图片,这就使得页面在加载是时候会发送很多http请求,当页面图片过多,会影响的页面的性能。所以,这里推荐使用url-loader。
{
test: /\.(png|jpg|jepg|svg|gif)$/,
use: [{
loader: 'url-loader',
options: {
limit: 10240, //这里的单位是b
name: 'image/[name][hash].[ext]' //打包后输出路径
}
}]
}
使用url-loader我们可以通过设置limit的值,将文件大小小于某个值的图片打包成base64的形式存放在打包后的 js 中,若超过了这个设定值,默认会使用file-loader(所以虽然代码没有配置 file-loader,但还是需要使用安装file-loader),并且会将配置的选项传递给file-loader。
4、import AMD 模块
有时我们需要在项目里使用一些 AMD 模块或者完全不支持模块化的库。例如移动端经常使用的 zepto。如果我们直接使用 import zepto 的方式引入是会报错的:Uncaught TypeError: Cannot read property 'createElement' of undefined
要使用也很简单,使用script-loader和exports-loader即可:
{
test: require.resolve('zepto'),
use: ['exports-loader?window.Zepto','script-loader']
}
四、plugins
webpack 可以使用插件(plugins)来让开发者能够在打包的过程中实现更多功能,插件会在整个构建过程中生效,并执行相关的任务。这里会介绍几个比较实用的插件:
1、mini-css-extract-plugin
在使用style-loader处理后,css 文件会作为模块打包进 js 文件里。若我们想将 js 文件和 css 文件分离。就可以使用mini-css-extract-plugin:
module: {
rules: [{
test: /\.css$/,
use: [{
loader: MiniCssExtractPlugin.loader
},
'css-loader'
]
}]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[hash].css'
})
]
2、copy-webpack-plugin
有时候我们会有一些没经过打包的文件需要复制到我们的生产目录里,copy-webpack-plugin就可以实现这个功能。
plugins: [
new CopyWebpackPlugin([
{
from: { glob: './src/public/*.htm', dot: true },
to: path.join(__setting.distTpl, 'public','[name].htm')
}
], { copyUnmodified: true })
]
3、html-webpack-plugin
我们前面介绍入口配置的时候会看到只配置了 js 文件,只是因为 webpack 现在入口只支持 js 文件,所以打包输出的也是 js 文件,那如果我们需要将 js 文件引入到 html 里,就需要使用到html-webpack-plugin插件。
html-webpack-plugin在使用的时候,是必须一个入口对应一个配置的,所以我们前面使用了多页面的配置,也需要进行相应的修改,修改后的getEntry方法:
const htmlPluginArray = [];
function getEntry() {
const entry = {};
//读取src目录所有page入口
glob.sync('./src/' + __setting.moduleId + '/*.js')
.forEach(function (filePath) {
var name = filePath.match(/\/src\/(.+)\/*.js/);
name = name[1];
if (!fs.existsSync(path.join(__projectDir, './src/' + name + '.htm'))) {
return;
}
entry[name] = ["core-js/stable", "regenerator-runtime/runtime", path.join(__projectDir, filePath)];
+ htmlPluginArray.push(new HtmlWebpackPlugin({
+ filename: `${__setting.distTpl}/${name}.htm`,
+ template: './src/' + name + '.htm',
+ inject: 'body',
+ minify: {
+ removeComments: true,
+ collapseWhitespace: true
+ },
+ chunks: [name],
+ inlineSource: '.(js|css)'
+ }))
});
return entry;
};
// 配置plugin,由于plugins通常使用数组类型来配置,
// 所以可以使用concat方法将配置好的html的数组添加进去。
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[hash].css'
})
].concat(htmlPluginArray),
里面的一些配置是要注意一下的:
filename 是配置需要将 html 改成什么名字并输出到哪里的配置。这里配置的的路径是以 output 里配置的path为相对路径的,我们上面 output 配置的是
path: path.join(__projectDir, __setting.distJs)
那最终的html输出路径就是
path.join(__projectDir, __setting.distJs,
`${__setting.distTpl}/${name}.htm`)
是将html里的代码进行压缩。如果 minify 选项设置为 true 或者配置对象 ( true 是 webpack 模式为 production 时的默认值),生成的 HTML 将使用 HTML-minifier压缩代码,更多具体的配置可以看这里minification。
template 生成 filename 文件的模版。重点:与 filename 的路径不同, 当匹配模版路径的时候将会从项目的根路径开始。
inject 制定 webpack 打包的 js css 静态资源插入到 html 的位置。
chunks 指定模板允许添加哪个入口文件。若不配置这个会将所有的入口都添加进来。
4、html-webpack-inline-source-plugin
若我们想将打包好的 js 代码 inline 进 html 的话,就要使用到html-webpack-inline-source-plugin
可以看到上面html-webpack-plugin的配置里有inlineSource: '.(js|css)'
这就是告诉html-webpack-inline-source-plugin需要将打包好的代码 inline 进 html 里,插件需要添加到html-webpack-plugin的配置后
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[hash].css'
})
].concat(htmlPluginArray).concat([
new HtmlWebpackInlineSourcePlugin()
])
但是html-webpack-inline-source-plugin也仅能将打包后输出的 js 文件引入 html,若你想将 html 码其他使用 script 标签加载的 js 文件或者 style 标签加载的 css 文件也 inline 进 html 里的话,html-webpack-inline-source-plugin并不能实现。从html-webpack-plugin里的 Issues 来看,html-webpack-plugin的作者也无意做这样的事情,但也给出了建议,可以借助html-webpack-plugin插件的 hooks html-webpack-plugin-before-html-processing达到我们需要的效果。
5、自定义插件
上面说到要将外部的静态文件也 inline 进 html,我们可以编写自定义插件,借助html-webpack-plugin插件的 hooks html-webpack-plugin-before-html-processing,再结合inline-source组件来实现我们的功能。
const {
inlineSource
} = require('inline-source');//加载inline-source组件
//定义方法
function scriptInlineHtml(options) {
// Configure your plugin with options...
this.options = options || {};
}
scriptInlineHtml.prototype.apply = function (compiler) {
let that = this;
(compiler.hooks ? //判断webpack版本,4.0以上和4.0以下的处理不一样
compiler.hooks.compilation.tap.bind(compiler.hooks.compilation, 'script-inline-html') :
compiler.plugin.bind(compiler, 'compilation'))(function (compilation) {
(compilation.hooks ?
compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing.tapAsync.bind(compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing, 'script-inline-html') :
compilation.plugin.bind(compilation, 'html-webpack-plugin-before-html-processing'))(async function (htmlPluginData, callback) {
//获取的html内容处理后重新赋值;
try {
htmlPluginData.html = await inlineSource(htmlPluginData.html, that.options);
// Do something with html
} catch (err) {
// Handle error
}
//继续执行下个插件
callback(null, htmlPluginData);
});
});
};
//webpack插件添加
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[hash].css'
})
].concat(htmlPluginArray).concat([
new scriptInlineHtml(),
new HtmlWebpackInlineSourcePlugin()
])
使用
<script src="/src/public/js/px2rem.js" inline></script>
这里结合 inline 静态资源,简单介绍了自定义插件的使用,在html-webpack-plugin构建 html 过程中,还提供其他一系列的事件。
Async:
Sync:
这些事件可以让我们在构建 html 的不同阶段里,通过一些处理来达到我们的目的。例如:可以结合smarty.js将使用了 smarty 的模板,引入一些模拟数据后解析成正常的html代码;读取 HTML 文件进行翻译文本的替换,实现页面的多语言化。打包不同皮肤的html文件等等。
五、其他配置
1、resolve
resolve 配置规定了 webpack 如何寻找各个依赖模块。
前面有讲到使用 alias 设置路径别名。在资源引用时,如果资源引用路径太深,又比较常用,我们可以定义路径别名,例如:
resolve: {
alias: {
'@': path.resolve(__projectDir, 'src')
}
}
我们就可以直接在代码中这样引用了:
let backimg = require("@/public/image/common/ico-back.png").default;
2、webpack dev server
webpack-dev-server是开发时的必备利器,它可以在本地起一个简单的 web 服务器,当文件发生变化时,能够实时重新加载。webpack-dev-server的配置也很简单:
devServer: {
contentBase: __projectDir, //页面的基础目录
publicPath:'/',
port: 8080,
host: '127.0.0.1',
open: true, //是否运行后自动打开浏览器
hot: true
}
启动 webpack-dev-server 后,在目标文件夹中是看不到编译后的文件的,实时编译后的文件都保存到了内存当中。
1) HMR
hot设置为 true 是启用 webpack 的 模块热替换( HMR )功能,但这里注意必须要添加插件webpack.HotModuleReplacementPlugin 才能完全启用 HMR
2) publicPath
publicPath 路径下的打包文件可以在浏览器中访问,webpack-dev-server 打包的内容是放在内存中的,并没有实际创建文件,这些打包后的资源对外的的根目录就是 publicPath。
默认 devServer.publicPath 是 '/',所以你的包( bundle )可以通过 http://127.0.0.1:8080/bundle.js 访问。注意:当这里的 publicPath 和 output 的 publicPath 同时设置时,这里的优先级更高。
总结
webpack 的配置能介绍的点其实还有很多,例如开发环境和生产环境进行配置分离;利用浏览器的缓存将公共的模块抽离分开打包;还有很多常用 plugins 插件等等。
这篇文章是以我在开发某个多页面应用项目为例,总结了一些我在webpack配置上的理解。希望能对浏览这篇文章的小伙伴有帮助。
作者:HZH
来源-微信公众号:三七互娱技术团队
出处:https://mp.weixin.qq.com/s/JzZDqe-f_NRMmdxDLXC7tQ
、概述
系统在启动过程中会做很多事情,有时候我们感觉不到,例如系统在启动的时候没有看到画面,就已经有打印信息出来了,那说明串口在uboot启动过程中已经初始化完成。
二、版本
rk uboot分为两个版本,一个是2014年的,一个是2017的,那么我们怎么区分系统目前是用的哪个版本?有两种方法:
1、确认根目录Makefile的版本号,列如:
#
## Chapter-1 SPDX-License-Identifier: GPL-2.0+
#
VERSION = 2017
PATCHLEVEL = 09
SUBLEVEL =
EXTRAVERSION =
NAME =
2、确认开机第一行正式打印信息,列如:
U-Boot 2017.09-01818-g11818ff-dirty (Nov 14 2019 - 11:11:47 +0800)
项目开源:v2017已开源且定期更新到Github:https://github.com/rockchip-linux/u-boot
内核版本:v2017要求RK内核版本 >= 4.4
三、2017版本uboot功能
v2017(next-dev) 是 RK 从 U-Boot 官方的 v2017.09 正式版本中切出来进行开发的版本,目前已经支持
RK 所有主流在售芯片。支持的功能主要有:
支持 RK Android 固件启动;
支持 Android AOSP 固件启动;
支持 Linux Distro 固件启动;
支持 Rockchip miniloader 和 SPL/TPL 两种 Pre-loader 引导;
支持 LVDS、EDP、MIPI、HDMI、CVBS、RGB 等显示设备;
支持 eMMC、Nand Flash、SPI Nand flash、SPI NOR flash、SD 卡、 U 盘等存储设备启动;
支持 FAT、EXT2、EXT4 文件系统;
支持 GPT、RK parameter 分区表;
支持开机 LOGO、充电动画、低电管理、电源管理;
支持 I2C、PMIC、CHARGE、FUEL GUAGE、USB、GPIO、PWM、GMAC、eMMC、NAND、
Interrupt 等;
支持 Vendor storage 保存用户的数据和配置;
支持 RockUSB 和 Google Fastboot 两种 USB gadget 烧写 eMMC;
支持 Mass storage、ethernet、HID 等 USB 设备;
支持通过硬件状态动态选择 kernel DTB;
四、驱动开发模型
DM (Driver Model) 是 U-Boot 标准的 device-driver 开发模型,跟 kernel 的 device-driver 模型非常类 似。v2017版本也遵循 DM 框架开发各功能模块。建议读者先阅读DM文档,了解DM架构原理和实现。
核心代码位于:
./drivers/core/
五、安全
U-Boot在ARM TrustZone的安全体系中属于Non-Secure World,无法直接访问任何安全的资源(如:
安全 memory、安全 otp、efuse...),需要借助 trust 间接访问。RK平台上U-Boot的CPU运行模式:
者:Lefex
转发链接:https://mp.weixin.qq.com/s/6J9X8s_QfQcv2g5Wr-qn-A
*请认真填写需求信息,我们会在24小时内与您取得联系。