版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
【移动应用开发技术】微信小程序工程化探索之webpack的示例分析
这篇文章将为大家详细讲解有关微信小程序工程化探索之webpack的示例分析,在下觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。前言微信小程序因为其便捷的使用方式,以极快的速度传播开来吸引了大量的使用者。市场需求急剧增加的情况下,每家互联网企业都想一尝甜头,因此掌握小程序开发这一技术无疑是一名前端开发者不可或缺的技能。但小程序开发当中总有一些不便一直让开发者诟病不已,主要表现在:初期缺乏方便的npm包管理机制(现阶段确实可以使用npm包,但是操作确实不便)不能使用预编译语言处理样式无法通过脚本命令切换不同的开发环境,需手动修改对应环境所需配置(常规项目至少具备开发与生产环境)无法将规范检查工具结合到项目工程中(诸如EsLint、StyleLint的使用)有了不少的问题之后,我开始思考如何将现代的工程化技术与小程序相结合。初期在社区中查阅资料时,许多前辈都基于gulp去做了不少实践,对于小程序这种多页应用来说gulp的流式工作方式似乎更加方便。在实际的实践过后,我不太满意应用gulp这一方案,所以我转向了对webpack的实践探索。我认为选择webpack作为工程化的支持,尽管它相对gulp更难实现,但在未来的发展中一定会有非凡的效果,实践我们先不考虑预编译、规范等等较为复杂的问题,我们的第一个目标是如何应用webpack将源代码文件夹下的文件输出到目标文件夹当中,接下来我们就一步步来创建这个工程项目:/*
创建项目
*/$
mkdir
wxmp-base$
cd
./wxmp-base/*
创建package.json
*/$
npm
init/*
安装依赖包
*/$
npm
install
webpack
webpack-cli
--dev复制代码安装好依赖之后我们为这个项目创建基础的目录结构,如图所示:上图所展示的是一个最简单的小程序,它只包含app全局配置文件和一个home页面。接下来我们不管全局或是页面,我们以文件类型划分为需要待加工的js类型文件和不需要再加工可以直接拷贝的wxml、wxss、json文件。以这样的思路我们开始编写供webpack执行的配置文件,在项目根目录下创建一个build目录存放webpack.config.js文件。$
mkdir
build$
cd
./build$
touch
webpack.config.js复制代码/**
webpack.config.js
*/const
path
=
require('path');const
CopyPlugin
=
require('copy-webpack-plugin');const
ABSOLUTE_PATH
=
process.cwd();module.exports
=
{
context:
path.resolve(ABSOLUTE_PATH,
'src'),
entry:
{
app:
'./app.js',
'pages/home/index':
'./pages/home/index.js'
},
output:
{
filename:
'[name].js',
path:
path.resolve(ABSOLUTE_PATH,
'dist')
},
module:
{
rules:
[
{
test:
/\.js$/,
exclude:
/node_modules/,
use:
{
loader:
'babel-loader',
options:
{
presets:
['@babel/preset-env'],
plugins:
['@babel/plugin-transform-runtime'],
},
},
}
]
},
plugins:
[
new
CopyPlugin([
{
from:
'**/*.wxml',
toType:
'dir',
},
{
from:
'**/*.wxss',
toType:
'dir',
},
{
from:
'**/*.json',
toType:
'dir',
}
])
]
};复制代码在编写完上述代码之后,为大家解释一下上述的代码究竟会做些什么:入口entry对象中我写了两个属性,意在将app.js和home/index.js作为webpack的构建入口,它会以这个文件为起始点创建各自的依赖关系,这样当我们在入口文件中引入其他文件时,被引入的文件也能被webpack所处理。module中我使用了babel-loader对js文件进行ES6转换为ES5的处理,并且加入了对新语法的处理,这样我们就解决了在原生小程序开发中总是要反复引入regenerator-runtime的问题。(这一步我们需要安装@babel/core、@babel/preset-env、@babel/plugin-transform-runtime、@babel/runtime、babel-loader这几个依赖包)使用copy-webpack-plugin来处理不需要再加工的文件,这个插件可以直接将文件复制到目标目录当中。我们了解完这些代码的实际作用之后就可以在终端中运行webpack--configbuild/webpack.config.js命令。webpack会将源代码编译到dist文件夹中,这个文件夹中的内容就可用在开发者工具中运行、预览、上传。优化完成了最基础的webpack构建策略后,我们实现了app和home页面的转化,但这还远远不够。我们还需要解决许多的问题:页面文件增多怎么办,组件怎么处理预期的预编译如何做规范如何结合到工程中环境变量怎么处理接下来我们针对以上几点进行webpack策略的升级:页面与组件一开始我的实现方法是写一个工具函数利用glob收集pages和components下的js文件然后生成入口对象传递给entry。但是在实践过程中,我发现这样的做法有两个弊端:当终端中已经启动了命令,这时候新增页面或组件都不会自动生成新的入口,也就是我们要重跑一遍命令。工具函数写死了匹配pages和components文件夹下的文件,不利于项目的延展性,如果我们需要分包或者文件夹命名需要改动时,我们就需要改动工具函数。本着程序员应该是极度慵懒,能交给机器完成的事情绝不自己动手的信条,我开始研究新的入口生成方案。最终确定下来编写一个webpack的插件,在webpack构建的生命周期中生成入口,废话不多说上代码:/**
build/entry-extract-plugin.js
*/const
fs
=
require('fs');const
path
=
require('path');const
chalk
=
require('chalk');const
replaceExt
=
require('replace-ext');const
{
difference
}
=
require('lodash');const
SingleEntryPlugin
=
require('webpack/lib/SingleEntryPlugin');const
MultiEntryPlugin
=
require('webpack/lib/MultiEntryPlugin');class
EntryExtractPlugin
{
constructor()
{
this.appContext
=
null;
this.pages
=
[];
this.entries
=
[];
}
/**
* 收集app.json文件中注册的pages和subpackages生成一个待处理数组
*/
getPages()
{
const
app
=
path.resolve(this.appContext,
'app.json');
const
content
=
fs.readFileSync(app,
'utf8');
const
{
pages
=
[],
subpackages
=
[]
}
=
JSON.parse(content);
const
{
length:
pagesLength
}
=
pages;
if
(!pagesLength)
{
console.log(chalk.red('ERROR
in
"app.json":
pages字段缺失'));
process.exit();
}
/**
收集分包中的页面
*/
const
{
length:
subPackagesLength
}
=
subpackages;
if
(subPackagesLength)
{
subpackages.forEach((subPackage)
=>
{
const
{
root,
pages:
subPages
=
[]
}
=
subPackage;
if
(!root)
{
console.log(chalk.red('ERROR
in
"app.json":
分包配置中root字段缺失'));
process.exit();
}
const
{
length:
subPagesLength
}
=
subPages;
if
(!subPagesLength)
{
console.log(chalk.red(`ERROR
in
"app.json":
当前分包
"${root}"
中pages字段为空`));
process.exit();
}
subPages.forEach((subPage)
=>
pages.push(`${root}/${subPage}`));
});
}
return
pages;
}
/**
* 以页面为起始点递归去寻找所使用的组件
* @param
{String}
当前文件的上下文路径
* @param
{String}
依赖路径
*
@param
{Array}
包含全部入口的数组
*/
addDependencies(context,
dependPath,
entries)
{
/**
生成绝对路径
*/
const
isAbsolute
=
dependPath[0]
===
'/';
let
absolutePath
=
'';
if
(isAbsolute)
{
absolutePath
=
path.resolve(this.appContext,
dependPath.slice(1));
}
else
{
absolutePath
=
path.resolve(context,
dependPath);
}
/**
生成以源代码目录为基准的相对路径
*/
const
relativePath
=
path.relative(this.appContext,
absolutePath);
/**
校验该路径是否合法以及是否在已有入口当中
*/
const
jsPath
=
replaceExt(absolutePath,
'.js');
const
isQualification
=
fs.existsSync(jsPath);
if
(!isQualification)
{
console.log(chalk.red(`ERROR:
in
"${replaceExt(relativePath,
'.js')}":
当前文件缺失`));
process.exit();
}
const
isExistence
=
entries.includes((entry)
=>
entry
===
absolutePath);
if
(!isExistence)
{
entries.push(relativePath);
}
/**
获取json文件内容
*/
const
jsonPath
=
replaceExt(absolutePath,
'.json');
const
isJsonExistence
=
fs.existsSync(jsonPath);
if
(!isJsonExistence)
{
console.log(chalk.red(`ERROR:
in
"${replaceExt(relativePath,
'.json')}":
当前文件缺失`));
process.exit();
}
try
{
const
content
=
fs.readFileSync(jsonPath,
'utf8');
const
{
usingComponents
=
{}
}
=
JSON.parse(content);
const
components
=
Object.values(usingComponents);
const
{
length
}
=
components;
/**
当json文件中有再引用其他组件时执行递归
*/
if
(length)
{
const
absoluteDir
=
path.dirname(absolutePath);
components.forEach((component)
=>
{
this.addDependencies(absoluteDir,
component,
entries);
});
}
}
catch
(e)
{
console.log(chalk.red(`ERROR:
in
"${replaceExt(relativePath,
'.json')}":
当前文件内容为空或书写不正确`));
process.exit();
}
}
/**
*
将入口加入到webpack中
*/
applyEntry(context,
entryName,
module)
{
if
(Array.isArray(module))
{
return
new
MultiEntryPlugin(context,
module,
entryName);
}
return
new
SingleEntryPlugin(context,
module,
entryName);
}
apply(compiler)
{
/**
设置源代码的上下文
*/
const
{
context
}
=
compiler.options;
this.appContext
=
context;
compiler.hooks.entryOption.tap('EntryExtractPlugin',
()
=>
{
/**
生成入口依赖数组
*/
this.pages
=
this.getPages();
this.pages.forEach((page)
=>
void
this.addDependencies(context,
page,
this.entries));
this.entries.forEach((entry)
=>
{
this.applyEntry(context,
entry,
`./${entry}`).apply(compiler);
});
});
compiler.hooks.watchRun.tap('EntryExtractPlugin',
()
=>
{
/**
校验页面入口是否增加
*/
const
pages
=
this.getPages();
const
diffPages
=
difference(pages,
this.pages);
const
{
length
}
=
diffPages;
if
(length)
{
this.pages
=
this.pages.concat(diffPages);
const
entries
=
[];
/**
通过新增的入口页面建立依赖
*/
diffPages.forEach((page)
=>
void
this.addDependencies(context,
page,
entries));
/**
去除与原有依赖的交集
*/
const
diffEntries
=
difference(entries,
this.entries);
diffEntries.forEach((entry)
=>
{
this.applyEntry(context,
entry,
`./${entry}`).apply(compiler);
});
this.entries
=
this.entries.concat(diffEntries);
}
});
}
}module.exports
=
EntryExtractPlugin;复制代码由于webpack的plugin相关知识不在我们这篇文章的讨论范畴,所以我只简单的介绍一下它是如何介入webpack的工作流程中并生成入口的。(如果有兴趣想了解这些可以私信我,有时间的话可能会整理一些资料出来给大家)该插件实际做了两件事:通过compiler的entryOption钩子,我们将递归生成的入口数组一项一项的加入entry中。通过compiler的watchRun钩子监听重新编译时是否有新的页面加入,如果有就会以新加入的页面生成一个依赖数组,然后再加入entry中。现在我们将这个插件应用到之前的webpack策略中,将上面的配置更改为:(记得安装chalkreplace-ext依赖)/**
build/webpack.config.js
*/const
EntryExtractPlugin
=
require('./entry-extract-plugin');module.exports
=
{
...
entry:
{
app:
'./app.js'
},
plugins:
[
...
new
EntryExtractPlugin()
]
}复制代码样式预编译与EsLint样式预编译和EsLint应用其实已经有许多优秀的文章了,在这里我就只贴出我们的实践代码:/**
build/webpack.config.js
*/const
MiniCssExtractPlugin
=
require('mini-css-extract-plugin');module.exports
=
{
...
module:
{
rules:
[
...
{
enforce:
'pre',
test:
/\.js$/,
exclude:
/node_modules/,
loader:
'eslint-loader',
options:
{
cache:
true,
fix:
true,
},
},
{
test:
/\.less$/,
use:
[
{
loader:
MiniCssExtractPlugin.loader,
},
{
loader:
'css-loader',
},
{
loader:
'less-loader',
},
],
},
]
},
plugins:
[
...
new
MiniCssExtractPlugin({
filename:
'[name].wxss'
})
]
}复制代码我们修改完策略后就可以将wxss后缀名的文件更改为less后缀名(如果你想用其他的预编译语言,可以自行修改loader),然后我们在js文件中加入import'./index.less'语句就能看到样式文件正常编译生成了。样式文件能够正常的生成最大的功臣就是mini-css-extract-plugin工具包,它帮助我们转换了后缀名并且生成到目标目录中。环境切换环境变量的切换我们使用cross-env工具包来进行配置,我们在package.json文件中添加两句脚本命令:"scripts":
{
"dev":
"cross-env
OPERATING_ENV=development
webpack
--config
build/webpack.config.js
--watch",
"build":
"cross-env
OPERATING_ENV=production
webpack
--config
build/webpack.config.js
}复制代码相应的我们也修改一下webpack的配置文件,将我们应用的环境也告诉webpack,这样webpack会针对环境对代码进行优化处理。/**
build/webpack.config.js
*/const
{
OPERATING_ENV
}
=
process.env;module.exports
=
{
...
mode:
OPERATING_ENV,
devtool:
OPERATING_ENV
===
'production'
?
'source-map'
:
'inline-source-map'}复制代码虽然我们也可以通过命令为webpack设置mode,这样也可以在项目中通过process.env.NODE_ENV访问环境变量,但是我还是推荐使用工具包,因为你可能会有多个环境uattestpre等等。针对JS优化小程序对包的大小有严格的要求,单个包的大小不能超过2M,所以我们应该对JS做进一步的优化,这有利于我们控制包的大小。我所做的优化主要针对runtime和多个入口页面之间引用的公共部分,修改配置文件为:/**
build/webpack.config.js
*/module.exports
=
{
...
optimization:
{
splitChunks:
{
cacheGroups:
{
commons:
{
chunks:
'initial',
name:
'commons',
minSize:
0,
maxSize:
0,
minChunks:
2,
},
},
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024年特种贵重物品搬运协议细则
- 2024年版房屋拆迁补偿合同
- 2024年版权许可使用合同协议
- 中专老师的工作计划范文
- 文明校园活动策划书(汇编15篇)
- 入职自我介绍集锦15篇
- 无源探测技术课程设计
- 植树节活动总结15篇
- 收银员的辞职报告范文集合10篇
- 小学数学骨干教师工作计划
- 医院药房年终工作总结
- 整体爬升钢平台模板工程技术规程
- 发动机无法启动的故障诊断
- 医疗机构医院临床微生物学检验标本的采集和转运指南
- 国开电大《员工招聘与配置》形考册第一次形考答案
- ODM合作方案教学课件
- 医药公司知识产权
- GB/T 1196-2023重熔用铝锭
- Revit软件学习实习报告
- 2024版国开电大本科《行政领导学》在线形考(形考任务一至四)试题及答案
- 风电教育培训体系建设
评论
0/150
提交评论