当前位置: 首页 > 后端技术 > Node.js

CabloyJS全栈开发之旅(一):NodeJS后端编译打包攻略

时间:2023-04-03 15:58:25 Node.js

背景毫无疑问,NodeJS全栈开发包括前端的NodeJS应用,以及后端的NodeJS应用?.CabloyJS前端使用Vue+Framework7,使用Webpack进行打包。CabloyJS后端是基于EggJS开发的上层框架。我们知道EggJS采用约定优于配置的原则。服务启动时会加载约定目录下的controller、service等文件。那么,我们基于EggJS开发的后端代码是不是也能像前端一样用Webpack打包呢?Meaning为什么会提出这样一个命题:NodeJS后端编译打包?因为NodeJS后端编译打包有以下两个显着的好处:1.保护商业代码编译打包,可以丑化源代码,满足保护商业代码的需要。虽然丑化javascript代码并不能完全避免反编译,但是我们必须基于一个原则:丑化的主要目的是为了保护开发团队的工作量。可想而知,反编译和基于反编译的二次开发工作量不小。2、提高启动性能编译打包可以将很多零散的javascript文件组合成一个文件,从而提高后端服务的启动性能。这在大型项目的开发中更有效。下面的案例我们以模块egg-born-module-test-party为例。本模块后端有63个js源码文件,编译打包后只生成一个backend.js文件。后端服务启动时,一个模块只需要加载一个文件,性能肯定比加载63个文件要好。如果一个大型项目包含100个业务模块,这种性能优势会更加明显。打包JS文件的工具有很多。由于CabloyJS前端使用Webpack进行打包,这里只讨论Webpack在后端的使用。我们知道,Webpack是从一个入口文件开始,检索require方法,得到一棵完整的文件依赖树,然后将这些依赖树合并成一个文件,最后进行ugl化。EggJS使用conventionsover配置的原则是文件之间的依赖关系是隐式约定的,而不是通过require显式声明。所以,在这种机制下,Webpack打包不起作用,但是EggJS的定位是框架的框架,这样我们就可以基于EggJS开发新的框架。CabloyJS后端在EggJS的基础上进一步扩展封装,可以通过require方法显式声明controller、service、middleware、config等定义文件,从而使Webpack提取出完整的文件依赖树,然后完成编译打包工作。本文的重点不是讲解CabloyJS后端如何扩展封装EggJS,而是讲解在已经实现require显式声明的前提下,NodeJS如何为后端egg-born的编译打包做准备-module-test-party是CabloyJS的测试模块,包含大量的测试用例。我们以这个模块为例来说明NodeJS后端编译打包的方方面面1.下载模块我们先将模块源码下载到本地$gitclonehttps://github.com/zhennann/egg-born-模块测试方。git如果没有git命令行工具,可以直接从GitHub官网下载:https://github.com/zhennann/e...2.安装依赖$npmi3.编译打包npmrunbuild:backend核心概念只要我们指定进入入口文件后,Webpack会自动通过require获取文件依赖树。因此,剩下的核心工作就是通过配置文件webpack.base.conf.js文件来调整Webpack的行为:/build/backend/webpack.base.conf.jsconstpath=require('path');constconfig=require('./config.js');constnodeModules={require3:'commonjs2require3',};functionresolve(dir){returnpath.join(__dirname,'../../backend',目录);}module.exports={entry:{backend:resolve('src/main.js'),},target:'node',output:{path:config.build.assetsRoot,文件名:'[name].js',库:'后端',libraryTarget:'commonjs2',},外部:nodeModules,解析:{扩展名:['.js','.json'],},模块:{规则:[],},节点:{console:false,global:false,process:false,__filename:false,__dirname:false,Buffer:false,setImmediate:false,},};1.entry/output通过entry/output的组合,我们指定了一个入口文件src/main.js,最终编译打包成一个输出文件backend.js2。target:'node'Webpack是一个通用的打包工具,既可以用于前端浏览器,也可以用于后端NodeJS。所以我们需要指定目标为node来打包后端NodeJS。比如在后端node场景下,一些内置模块会被排除在包之外,比如fs、path等。3.为了让原本为后端NodeJS开发的代码在前端浏览器中运行,Webpack提供模拟策略。比如global、process、__filename、__dirname都是NodeJS的内置对象。如果这些对象包含在代码中,并且代码需要在前端运行,则需要模拟。这里讨论的是后端编译,所以我们直接赋false来禁用模拟行为4.resolve.extensions如果我们在使用require引用源文件的时候没有指定文件扩展名,那么Webpack会帮我们通过resolve.extensions匹配合适的文件名5.module.rules除了打包js文件,Webpack还可以打包css/image/text等资源文件。因为这是后端封装,所以不需要设置module.rules6。externals这里的重点在于,在nodeexternals的实际业务开发中,我们难免会用到大量的第三方模块,这些模块一般安装在node_modules目录下,比如moment.因为我们也通过constmoment=require('moment')引用了第三方库,所以Webpack也会尝试打包moment。一方面,有大量的第三方模块。如果将它们打包,最终输出的文件会太大。另一方面,保护商业代码没有任何意义。因此,我们需要想办法将这些第三方模块排除在打包的依赖树之外——Excludemoment如果要排除moment,我们可以这样配置:externals:{moment:'commonjs2moment'}-Excludenode_modules如果我们想排除node_modules目录下的所有第三方模块,可以这样配置:varfs=require('fs');varnodeModules={};fs.readdirSync('node_modules').filter(function(x){return['.bin'].indexOf(x)===-1;}).forEach(function(mod){nodeModules[mod]='commonjs2'+mod;});module.exports={...externals:nodeModules...}-一个更优雅的策略针对这个场景,CabloyJS开发了一个NPM模块require3:https://github.com/zhennann/require3我们只需要排除externals中的模块require3即可。其余模块通过require3引用,轻松避免被打包的行为constnodeModules={require3:'commonjs2require3',};module.exports={...externals:nodeModules...}在实际业务代码中,一般这样引用:constrequire3=require('require3');constmoment=require3('moment');moment被require3引用,避免被webpack打包。webpack.prod.conf.js文件:/build/backend/webpack.prod.conf.jsconstwebpack=require('webpack');constconfig=require('./config.js');constmerge=require('webpack-merge');constbaseWebpackConfig=require('./webpack.base.conf');constenv=config.build.env;constplugins=[newwebpack.DefinePlugin({'process.env':env,}),];constwebpackConfig=合并(baseWebpackConfig,{模式:'生产',devtool:config.build.productionSourceMap?'source-map':false,插件,优化:{runtimeChunk:false,splitChunks:false,最小化:config.build.uglify,},});module.exports=webpackConfig;1.mode:'production'通过指定mode为production,指示Webpack使用内置的production相关的优化策略2.devtool指的是表示webpack是否生成sourcemap文件,如果是,sourcemap的文件格式是什么,详细格式列表请参考:https://webpack.js.org/configuration/devtool/3。optimization.minimize由于我们只需要输出单个文件,所以只需要指明Webpack是否需要通过优化进行最小化(uglified)。该文件已被丑化。不过也有网友认为这项工作还不够,希望打包后的文件能再乱一点。接下来,我们将使用babel对js文件做进一步的代码翻译工作。先放开配置,然后逐一解释文件:/build/backend/webpack.base.conf.js...module:{rules:[{test:/\.js$/,exclude:/node_modules/,使用:{loader:'babel-loader',选项:{babelrc:false,//预设:['@babel/preset-env'],插件:['@babel/plugin-transform-arrow-functions','@babel/plugin-transform-for-of','@babel/plugin-transform-parameters','@babel/plugin-transform-shorthand-properties','@babel/plugin-transform-spread','@babel/plugin-transform-template-literals','@babel/plugin-proposal-object-rest-spread','@babel/plugin-transform-async-to-generator',],},},},],},...1。test我们只对后缀为.js的文件进行babel翻译2.exclude排除node_modules目录下的js文件3.use.loader使用babel-loader翻译js文件4.use.optionsbabel-loader翻译参数4.1babelrc:false翻译参数可以在options,或者在项目根目录下创建一个.babelrc文件,然后在文件中进行配置这里我们直接在options中配置翻译参数4.2presetsBabel的翻译是通过一系列插件的组合完成的插件。我们可以定义一系列插件的组合作为预设。@babel/preset-env是babel提供的预配置组合,包含大量插件。但是,如果这些预先配置的插件组合生效,会破坏后端NodeJS代码的一些特性,从而导致意想不到的问题。因此,我们将presets参数注释掉,手动添加我们需要的插件组合。4.3插件启用了太多的babel插件。一方面会影响编译效率。另一方面,一些babel插件会破坏一些后端的NodeJS代码。特性,导致意想不到的问题。经过实际测试,启用以下babel插件可以将后端NodeJS代码翻译成惨不忍睹的状态。前面我们也提到了一个原则:丑化的主要目的是保护开发团队的工作量插件名称使用arrow-functions翻译箭头函数for-of翻译for-of循??环参数翻译ES2015函数参数速记-properties翻译速记propertyspreadtranslation...展开形式template-literalstranslationtemplatestringobject-rest-spreadtranslationobjectexpansionexpressionasync-to-generator将async方法翻译成generatorasync/await本质上是generator+Promise的语法糖。因此,将异步方法转化为生成器,不仅可以显着打乱NodeJS代码的逻辑流程,还可以回归本质,提高NodeJS代码的性能。更详细的Babel插件可以参考:https://babeljs.io/docs/en/plugins编译打包最后我们再次执行NodeJS后端的编译打包命令npmrunbuild:backend