VPS参考测评推荐
专注分享VPS主机优惠信息
衡天云优惠活动
华纳云最新优惠促销活动
jtti最新优惠促销活动

微信小程序工程探索实用篇--- webpack(微信小程序工程结构)

locvps
主机参考:VPS测评参考推荐/专注分享VPS服务器优惠信息!若您是商家可以在本站进行投稿,查看详情!此外我们还提供软文收录、PayPal代付、广告赞助等服务,查看详情!
我们发布的部分优惠活动文章可能存在时效性,购买时建议在本站搜索商家名称可查看相关文章充分了解该商家!若非中文页面可使用Edge浏览器同步翻译!PayPal代付/收录合作

微信小程序工程探索实用篇--- webpack(微信小程序工程结构)

前言微信小程序因为其便捷的使用方式,以极快的速度传播开来吸引了大量的使用者。市场需求急剧增加的情况下,每家互联网企业都想一尝甜头,因此掌握小程序开发这一技术无疑是一名前端开发者不可或缺的技能。但小程序开发当中总有一些不便一直让开发者诟病不已,主要表现在:

初期缺乏方便的npm & # 21253管理机制(现阶段确实可以使用npm & # 21253,但是操作确实不便)不能使用预编译语言处理样式无法通过脚本命令切换不同的开发环境,需手动修改对应环境所需配置(常规项目至少具备开发与生产环境)无法将规范检查工具结合到项目工程中(诸如EsLint & # 12289StyleLint & # 30340使用)有了不少的问题之后,我开始思考如何将现代的工程化技术与小程序相结合。初期在社区中查阅资料时,许多前辈都基于一饮而尽& # 21435;做了不少实践,对于小程序这种多页应用来说一饮而尽& # 30340;流式工作方式似乎更加方便。在实际的实践过后,我不太满意应用一饮而尽& # 36825;一方案,所以我转向了对webpack & # 30340实践探索。我认为选择webpack & # 20316为工程化的支持,尽管它相对一饮而尽& # 26356;难实现,但在未来的发展中一定会有非凡的效果,

实践我们先不考虑预编译、规范等等较为复杂的问题,我们的第一个目标是如何应用webpack & # 23558源代码文件夹下的文件输出到目标文件夹当中,接下来我们就一步步来创建这个工程项目:

/* 创建项目*/$ mkdir wxmp-base$ cd。/wxmp -base/* & # 21019;建package . JSON */$ NPM init/* & # 23433;装依赖包*/$ npm安装web pack web pack -CLI --dev & # 22797;制代码安装好依赖之后我们为这个项目创建基础的目录结构,如图所示:

微信小程序工程探索实用篇--- webpack(微信小程序工程结构)上图所展示的是一个最简单的小程序,它只包含app & # 20840局配置文件和一个首页& # 39029;面。接下来我们不管全局或是页面,我们以文件类型划分为需要待加工的js & # 31867型文件和不需要再加工可以直接拷贝的wxml & # 12289wxss & # 12289json & # 25991件。以这样的思路我们开始编写供webpack & # 25191行的配置文件,在项目根目录下创建一个构建& # 30446;录存放web pack . config . js & # 25991;件。

$ mkdir build$ cd。/build $ touch web pack . config . js & # 22797;制代码/* * web pack . config . js */const path = require(' path ');const copy plugin = require(' copy -web pack -plugin ');const ABSOLUTE _ PATH = process . CWD();module . exports = { context:PATH . resolve(ABSOLUTE _ PATH,' src '),条目:{ app:'。/app.js ',' pages/home/index ':'。/pages/home/index.js' },输出:{文件名:'[名称]。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 copy plugin([{ from:' * */*。wxml ',toType: 'dir ',},{ from: '**/*。wxss ',toType: 'dir ',},{ from: '**/*。json ',toType: 'dir ',}])]};复制代码在编写完上述代码之后,为大家解释一下上述的代码究竟会做些什么:

入口条目& # 23545;象中我写了两个属性,意在将app.js & # 21644home/index . js & # 20316;为webpack & # 30340构建入口,它会以这个文件为起始点创建各自的依赖关系,这样当我们在入口文件中引入其他文件时,被引入的文件也能被webpack & # 25152处理。模块& # 20013;我使用了babel -loader & # 23545;js & # 25991件进行ES6 & # 36716换为ES5 & # 30340处理,并且加入了对新语法的处理,这样我们就解决了在原生小程序开发中总是要反复引入再生器-运行时& # 30340;问题。(这一步我们需要安装@ babel/core & # 12289;@ babel/preset -env & # 12289;@ babel/plugin -transform -runtime & # 12289;@ babel/runtime & # 12289;babel -loader & # 36825;几个依赖包)使用copy -web pack -plugin & # 26469;处理不需要再加工的文件,这个插件可以直接将文件复制到目标目录当中。我们了解完这些代码的实际作用之后就可以在终端中运行web pack --config build/web pack . config . js & # 21629;令。webpack & # 20250将源代码编译到dist & # 25991件夹中,这个文件夹中的内容就可用在开发者工具中运行、预览、上传。

优化完成了最基础的网络包& # 26500;建策略后,我们实现了app & # 21644首页& # 39029;面的转化,但这还远远不够。我们还需要解决许多的问题:

页面文件增多怎么办,组件怎么处理预期的预编译如何做规范如何结合到工程中环境变量怎么处理接下来我们针对以上几点进行webpack & # 31574略的升级:

页面与组件一开始我的实现方法是写一个工具函数利用glob & # 25910集页数& # 21644;组件& # 19979;的js & # 25991件然后生成入口对象传递给条目& # 12290;但是在实践过程中,我发现这样的做法有两个弊端:

当终端中已经启动了命令,这时候新增页面或组件都不会自动生成新的入口,也就是我们要重跑一遍命令。工具函数写死了匹配页数& # 21644;组件& # 25991;件夹下的文件,不利于项目的延展性,如果我们需要分包或者文件夹命名需要改动时,我们就需要改动工具函数。本着程序员应该是极度慵懒,能交给机器完成的事情绝不自己动手的信条,我开始研究新的入口生成方案。最终确定下来编写一个webpack & # 30340插件,在网络包& # 26500;建的生命周期中生成入口,废话不多说上代码:

/* * build/entry -extract -plugin . js */const fs = require(' fs ');const path = require(' path ');const chalk = require(' chalk ');const replace ext = require(' replace -ext ');const { difference } = require(' lodash ');const singletentryplugin = require(' web pack/lib/singletentryplugin ');const MultiEntryPlugin = require(' web pack/lib/MultiEntryPlugin ');class EntryExtractPlugin { constructor(){ this . app context = null;this . pages =[];this . entries =[];} /** *收集app.json & # 25991件中注册的页数& # 21644;子包& # 29983;成一个待处理数组*/get pages(){ const app = path . resolve(this . app context,' app . JSON ');const content = fs . read file sync(app,' utf8 ');const { pages = [],subpackages =[]} = JSON . parse(content);const { length:pagesLength } = pages;如果(!pages length){ console . log(chalk . red(' app . JSON中的错误):pages & # 23383段缺失'));process . exit();} /** 收集分包中的页面*/const { length:subpackages length } = subpackages;if(sub packages length){ sub packages . foreach((sub package)= & gt;{ const { root,pages:subPages =[]} = subPackage;如果(!root){ console . log(chalk . red(' app . JSON中的错误):& # 20998;包配置中root & # 23383段缺失'));process . exit();} const { length:subPagesLength } = subPages;如果(!subpage length){ console . log(chalk . red(` app . JSON中的错误):& # 24403;前分包" $ { root } " & # 20013pages & # 23383段为空`));process . exit();} subpages . foreach((subPage)= & gt;pages . push(` $ { root }/$ { subPage } `);});}返回页面;} /** *以页面为起始点递归去寻找所使用的组件* @ param { String } & # 24403前文件的上下文路径* @ param { String } & # 20381赖路径* @ param { Array } & # 21253含全部入口的数组*/ addDependencies(context,dependPath,entries){/* * & # 29983;成绝对路径*/const isab solute = depend path[0]= = = '/';let absolute path =“”;if(isab solute){ absolute path = path . resolve(this . app context,depend path . slice(1));} else { absolute path = path . resolve(context,depend path);} /** 生成以源代码目录为基准的相对路径*/const relative path = path . relative(this . app context,absolute path);/** 校验该路径是否合法以及是否在已有入口当中*/const jsPath = replace ext(absolute path,'。js’);const is qualification = fs . exists sync(jsPath);如果(!is qualification){ console . log(chalk . red(` error:in " $ { replace ext(relative path,'。js ')} ":& # 24403;前文件缺失`));process . exit();} const is existence = entries . includes((entry)= & gt;entry = = = absolute path);如果(!is existence){ entries . push(relative path);} /** 获取json & # 25991件内容*/const JSON path = replace ext(absolute path,'。JSON’);const isJsonExistence = fs . existssync(JSON path);如果(!isjsone existence){ console . log(chalk . red(` error:in " $ { replace ext(relative path,'。JSON ')} ":& # 24403;前文件缺失`));process . exit();} try { const content = fs . read file sync(JSON path,' utf8 ');const { using components = { } } = JSON . parse(content);const components = object . values(using components);const { length } =组件;/** 当json & # 25991件中有再引用其他组件时执行递归*/if(length){ const absolute dir = path . dirname(absolute path);components . foreach((component)= & gt;{ this . add dependencies(absolute dir,component,entries);});} } catch(e){ console . log(chalk . red(` error:in " $ { replace ext(relative path,'。JSON ')} ":& # 24403;前文件内容为空或书写不正确`));process . exit();} } /** * 将入口加入到网络包& # 20013;*/ applyEntry(context,entryName,module){ if(array . is array(module)){ return new MultiEntryPlugin(context,module,entry name);}返回新的SingleEntryPlugin(context,module,entry name);} apply(编译器){/* * & # 35774;置源代码的上下文*/ const { context } =编译器.选项;this.appContext = contextcompiler . hooks . entry option . tap(' EntryExtractPlugin ',()= & gt{ /** 生成入口依赖数组*/this . pages = this . get pages();this . pages . foreach((page)= & gt;void this . add dependencies(context,page,this . entries));this . entries . foreach((entry)= & gt;{ this.applyEntry(context,Entry,`。/${entry} `)。apply(编译器);});});compiler . hooks . watch run . tap(' EntryExtractPlugin ',()= & gt{ /** 校验页面入口是否增加*/const pages = this . get pages();const diff pages = difference(pages,this . pages);const { length } = diffPagesif(length){ this . pages = this . pages . concat(diffPages);const entries =[];/** 通过新增的入口页面建立依赖*/diff pages . foreach((page)= & gt;void this.addDependencies(上下文、页面、条目));/** 去除与原有依赖的交集*/const diff entries = difference(entries,this . entries);diff entries . foreach((entry)= & gt;{ this.applyEntry(context,Entry,`。/${entry} `)。apply(编译器);});this . entries = this . entries . concat(diff entries);} });} } module . exports = EntryExtractPlugin;复制代码由于webpack & # 30340插件& # 30456;关知识不在我们这篇文章的讨论范畴,所以我只简单的介绍一下它是如何介入webpack & # 30340工作流程中并生成入口的。(如果有兴趣想了解这些可以私信我,有时间的话可能会整理一些资料出来给大家)该插件实际做了两件事:

通过编译器& # 30340;entryOption & # 38057子,我们将递归生成的入口数组一项一项的加入参赛作品& # 20013;。通过编译器& # 30340;watchRun & # 38057子监听重新编译时是否有新的页面加入,如果有就会以新加入的页面生成一个依赖数组,然后再加入参赛作品& # 20013;。现在我们将这个插件应用到之前的webpack & # 31574略中,将上面的配置更改为:(记得安装粉笔替代品-ext & # 20381;赖)

/* * build/web pack . config . js */const EntryExtractPlugin = require('。/entry -extract -plugin ');模块.导出= {...条目:{应用:'。/app.js' },插件:[...new EntryExtractPlugin()]} & # 22797;制代码样式预编译与EsLint & # 26679式预编译和EsLint & # 24212用其实已经有许多优秀的文章了,在这里我就只贴出我们的实践代码:

/* * build/web pack . config . js */const minicsextractplugin = require(' mini -css-extract -plugin ');模块.导出= {...模块:{规则:[...{ enforce: 'pre ',test: /\。js$/,exclude: /node_modules/,loader: 'eslint-loader ',选项:{ cache: true,fix: true,},},{ test: /\。less$/,use:[{ loader:minicsextractplugin . loader,},{ loader: 'css-loader ',},{ loader: 'less-loader ',},],},] },plugins: [...新建minicsextractplugin({ filename:'[name].wxss ' })]} & # 22797;制代码我们修改完策略后就可以将wxss & # 21518缀名的文件更改为少& # 21518;缀名(如果你想用其他的预编译语言,可以自行修改装载机& # 65289;,然后我们在js & # 25991件中加入“导入”。/index . less ' & # 35821;句就能看到样式文件正常编译生成了。样式文件能够正常的生成最大的功臣就是mini-css-extract -plugin & # 24037;具包,它帮助我们转换了后缀名并且生成到目标目录中。

环境切换环境变量的切换我们使用cross -env & # 24037;具包来进行配置,我们在package.json & # 25991件中添加两句脚本命令:

" scripts ":{ " dev ":" cross -ENV OPERATING _ ENV =开发web pack --config build/web pack . config . js --watch "," build ":" cross -ENV OPERATING _ ENV =生产web pack --config build/web pack . config . js } & # 22797;制代码相应的我们也修改一下webpack & # 30340配置文件,将我们应用的环境也告诉webpack & # 65292这样webpack & # 20250针对环境对代码进行优化处理。

/* * build/web pack . config . js */const { OPERATING _ ENV } = process . ENV;模块.导出= {...mode: OPERATING_ENV,dev tool:OPERATING _ ENV = = = ' production '?' source -map ':' inline -source -map ' } & # 22797;制代码虽然我们也可以通过命令为webpack & # 35774置模式& # 65292;这样也可以在项目中通过process . ENV . node _ ENV & # 35775;问环境变量,但是我还是推荐使用工具包,因为你可能会有多个环境uat测试前& # 31561;等。

针对JS & # 20248化小程序对包的大小有严格的要求,单个包的大小不能超过2M & # 65292;所以我们应该对JS & # 20570进一步的优化,这有利于我们控制包的大小。我所做的优化主要针对运行时& # 21644;多个入口页面之间引用的公共部分,修改配置文件为:

/* * build/web pack . config . js */module . exports = {...优化:{ split chunks:{ cache groups:{ commons:{ chunks:' initial ',name: 'commons ',minSize: 0,maxSize: 0,minChunks: 2,},},},runtimeChunk: { name: 'manifest ',},} & # 22797;制代码webpack & # 20250将公共的部分抽离出来在dist & # 25991件夹根目录中生成common.js & # 21644manifest.js & # 25991件,这样整个项目的体积就会有明显的缩小,但是你会发现当我们运行命令是开发者工具里面项目其实是无法正常运行的,这是为什么?

这主要是因为这种优化使小程序其他的js & # 25991件丢失了对公共部分的依赖,我们对webpack & # 37197置文件做如下修改就可以解决了:

/* * build/web pack . config . js */module . exports = {...输出:{...globalObject: 'global' },plugins: [ new webpack。banner plugin({ banner:' const commons = require("。/commons”);\nconst runtime = require("。/runtime ");',raw: true,include: 'app.js ',})]} & # 22797;制代码小小解惑许多读者可能会有疑惑,为什么你不直接使用已有的框架进行开发,这些能力已经有许多框架支持了。选择框架确实是一个不错的选择,毕竟开箱即用为开发者带来了许多便利。但是这个选择是有利有弊的,我也对市面上的较流行框架做了一段时间的研究和实践。较为早期的腾讯的wepy & # 12289美团的mpvue & # 65292后来者居上的京东的太郎& # 12289;Dcloud & # 30340uni-app & # 31561;,这些在应用当中我认为有以下一些点不受我青睐:

黑盒使我们有时很难定位问题究竟是出在自身的代码当中还是在框架的编译流程中(这让我踩了不少坑)围绕框架展开的可以使用的资源有限,例如UI & # 30340使用基本依赖于官方团队进行配套开发,如果没有社区也极难找到需要的资源(这一点我认为uni-app & # 30340;社区做得挺不错)与已有的一些原生的资源无法结合,这些框架基本都是基于编译原理提供了以react & # 25110者vue & # 20026开发语言的能力,这使得原生的资源要无缝接入很难实现(假如你们公司已经积淀了一些业务组件那你会很头疼)。最后一点,也是我担心的最重要的一点,框架的升级速度是否能跟得上官方的迭代速度,如果滞后了已有的项目该如何处理以上基本是我为什么要自己探索小程序工程化的理由(其实还有一点就是求知欲,嘻嘻)

写在最后以上是我对原生小程序工程化的探索,在我所在的团队中还应用了一些相关的样式规范,在这篇文章中我没有具体的说,有兴趣的话可以查看我的专栏中《团队规范之样式规范实践》一文。其实还有静态资源的管理,项目的目录的补充这些细节可以依照团队的需要去完善补充。本文希望对有需要做这方面实践的团队有所帮助,如有观点不正确或需要改进的地方,望可以评论告知我。

以上是实战篇---微信小程序工程探索的webpack的详细内容。更多信息请关注主机参考其他相关文章!

这几篇文章你可能也喜欢:

本文由主机参考刊发,转载请注明:微信小程序工程探索实用篇--- webpack(微信小程序工程结构) https://zhujicankao.com/96770.html

【腾讯云】领8888元采购礼包,抢爆款云服务器 每月 9元起,个人开发者加享折上折!
打赏
转载请注明原文链接:主机参考 » 微信小程序工程探索实用篇--- webpack(微信小程序工程结构)
主机参考仅做资料收集,不对商家任何信息及交易做信用担保,购买前请注意风险,有交易纠纷请自行解决!请查阅:特别声明

评论 抢沙发

评论前必须登录!