内容简介:这段时间看了一些1、
一、前言
这段时间看了一些 sfx 的源码,收获颇深。本想找个时间更新一篇文章,但是最近事情比较多,没有时间去整理这些东西。趁这两天闲了下来,便整理了一下,然后跟大家分享一下。
二、SFX介绍
sfx 是一款优秀的用于迅速构建Web的应用工具,开发者只需要关注项目逻辑的代码,而不需要关心webpack打包、搭建本地Node服务等等诸如此类的这些问题。是一款基于模板化的开发工具,也就是把已经搭建好的项目结构给照搬过来,所有的配置都是暴露出来的,并且可以根据实际情况去做一些自定义配置,更加灵活自由,并且可以直接接入已有项目,并带来本地开发环境和生产环境的收益。
目前sfx2.x版本主要支持以下几个功能:
- sfx2 init <projectName> 初始化项目模板文件
- sfx2 dev [address] 使用sfx内置的node服务搭建本地开发环境,且可传入自定义webpack配置
- sfx2 build [tasks...] 使用sfx内置的webpack4进行打包构建,且可传入自定义webpack配置
-
sfx2 eslint [files...] 使用sfx内置的eslint进行代码规范校验,且可传入指定目录文件
-
sfx2 unit [files...] 使用sfx内置的 karma 进行单元测试 ,且可传入指定目录文件
三、为什么我们要使用统一脚手架
1、 可以减少开发人员配置脚手架带来的时间损耗;
2、 统一项目结构,方便管理,降低项目交接时带来熟悉项目的时间;
3、 方便统一技术栈,预先引入固定的组件库;
4、 提高开发人员在多个项目之间的快速切换能力,提高项目可维护性,统一公司技术栈,避免因为环境不同导致奇怪的问题。
四、SFX项目结构
整个项目的目录结构如上图所示,下面我大概介绍每个文件夹的东西大致都是干嘛的。
1、bin (存放 sfx 的 入口文件 ,这里放的 sfx 的一些命令文件,比如 sfx init 这样的命令都是从由这里控制的)
2、 docs (一些 sfx的 项目介绍和使用注意事项啥的)
3、eslint (存放 sfx 进行 eslint 代码校验的自定义方法)
3、 lib (这里存放着一些需要的一些自定义方法)
4、 node_modules (第三方node模块 )
5、template (存放sfx进行 init 模板初始化的自定义方法与项目结构的模板)
6、util (存放封装的 工具 函数)
7、webpack (存放 sfx 进行 dev 开启本地node开发环境, build 打包 构建生产环境, unit 进行单元测试的自定义方法和相关webpack配置文件)
8、 一些杂七杂八的东西 (比如eslint配置、.gitignore、LICENSE等等诸如此类这些东西,不影响我们阅读源码,可以直接忽略掉。)
9、 package.json/README.md
五、分析源码
1、package.json
按常规来说,了解一个项目,首先看 package.json :
{ "name": "@sxf/sfx2", "version": "2.1.5", "description": "sfx cli", "main": "index.js", "license": "MIT", "files": [ "bin", "eslint", "lib", "template", "util", "webpack" ], "keywords": [ "sfx", "sfx2" ], "bin": { "sfx2": "bin/sfx2" }, "homepage": "http://code.sangfor.org/UED/FE-COMMON/sfx", "bugs": { "url": "http://code.sangfor.org/UED/FE-COMMON/sfx/issues" }, "author": { "email": "43115@sangfor.com", "name": "zhangyuantao" }, "scripts": { "lint": "eslint" }, "dependencies": { "@babel/core": "^7.1.2", "@babel/plugin-transform-reserved-words": "^7.0.0", "@babel/preset-env": "^7.1.0", "@cgroup/eslint-plugin-sfchecklist": "http://code.sangfor.org/UED/FE-COMMON/eslint-plugin-sfchecklist/-/archive/v2.0.0/eslint-plugin-sfchecklist-v2.0.0.tar.gz", "@sxf/eslint-plugin-sfchecklist": "^3.1.3", "babel-eslint": "^10.0.1", "babel-loader": "^8.0.4", "babel-plugin-transform-es3-member-expression-literals": "^6.22.0", "babel-plugin-transform-es3-property-literals": "^6.22.0", "babel-plugin-transform-member-expression-literals": "^6.9.4", "babel-plugin-transform-property-literals": "^6.9.4", "babel-polyfill": "^6.26.0", "cache-loader": "^1.2.2", "chai": "^4.2.0", "commander": "^2.19.0", "copy-webpack-plugin": "^4.5.4", "css-loader": "^1.0.0", "eslint": "^5.7.0", "eslint-friendly-formatter": "^4.0.1", "eslint-loader": "^2.1.1", "eslint-plugin-html": "^4.0.6", "eslint-plugin-vue": "^5.0.0-beta.3", "eventsource-polyfill": "^0.9.6", "express": "^4.16.4", "file-loader": "^2.0.0", "happypack": "^5.0.0", "html-webpack-plugin": "^4.0.0-beta.5", "http-proxy": "^1.17.0", "is-glob": "^4.0.0", "istanbul": "^0.4.5", "istanbul-instrumenter-loader": "^3.0.1", "karma": "^3.1.1", "karma-chai": "^0.1.0", "karma-chrome-launcher": "^2.2.0", "karma-coverage": "^1.1.2", "karma-coverage-istanbul-reporter": "^2.0.4", "karma-mocha": "^1.3.0", "karma-mocha-reporter": "^2.2.5", "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "^3.0.5", "less": "^3.8.1", "less-loader": "^4.1.0", "lodash": "^4.17.11", "log4js": "^3.0.6", "micromatch": "^3.1.10", "mini-css-extract-plugin": "^0.4.4", "mocha": "^5.2.0", "opn": "^5.4.0", "ora": "^3.0.0", "phantomjs-polyfill": "^0.0.2", "postcss-initial": "^3.0.0", "postcss-loader": "^3.0.0", "sass-loader": "^7.1.0", "scss-loader": "^0.0.1", "shelljs": "^0.8.2", "style-loader": "^0.23.1", "stylus": "^0.54.5", "stylus-loader": "^3.0.2", "ts-loader": "^5.2.2", "typescript": "^3.1.4", "uglifyjs-webpack-plugin": "^2.0.1", "underscore.template": "^0.1.7", "url-loader": "^1.1.2", "vue-hot-reload-api": "^2.3.1", "vue-loader": "^15.4.2", "vue-style-loader": "^4.1.2", "vue-template-compiler": "^2.5.17", "webpack": "^4.22.0", "webpack-dev-middleware": "^3.4.0", "webpack-hot-middleware": "^2.24.3", "webpack-merge": "^4.1.4" }, "devDependencies": { "docsify": "3.7.2", "karma-phantomjs-launcher": "^1.0.4" }}复制代码
从上述代码中,我们可以看到在 package.json 提供一个映射到本地本地文件名的 bin 字段,一旦被引入后, npm 将软链接这个文件到prefix/bin里面,以便于全局引入,或者在./node_modules/.bin/目录里,也就是说在npm link执行之后,就能在全局使用sfx2这个指令来执行bin/sfx2文件了。
2、bin/sfx2
#!/usr/bin/env node"use strict"; let commander = require('commander'); let sfx2 = require('../index'); const PACKAGE_JSON = require('../package.json'); console.log(`Welcome to use sfx cli tool (version: ${PACKAGE_JSON.version}), use "sfx2 --help" show more options.`); commander .version(PACKAGE_JSON.version) .usage('<command> [options]') .option('-n, --nolint', 'remove eslint') .option('-c, --config [file]', 'sfx.config.js file') .description(` For Example: sfx init vue sfx build --nolint sfx dev --config=custom.sfx.config.js sfx eslint `); commander .command('init <template>') .option('-i, --install', 'auto run yarn install') .option('-b, --build', 'auto run sfx build') .description('init folder like yeoman') .action((template, options) => { sfx2.init(template, { install: options.install, build: options.build }); }); commander .command('build [tasks...]') .description('build for production. [thirdParts, project]') .action((tasks) => { parseOption(commander); sfx2.build(tasks).catch(err => { console.error(err); process.exit(-1); }); }); commander .command('dev [address]') .description('run a local server for debug') .action(address => { parseOption(commander); sfx2.dev(address); }); commander .command('eslint [files...]') .description('run eslint') .action((files) => { let ret = sfx2.eslint(files); if (ret === false) { process.exit(-1); } }); commander .command('unit [files...]') .option('-s, --single', 'single run') .option('-e, --auto-exit', 'auto exit when karma completed') .description('run unit test task') .action((files, options) => { parseOption(commander); sfx2.unit(files, { single: options.single, autoExit: options.autoExit }); }); commander.parse(process.argv); function parseOption (commander) { if (commander.nolint) { process.env.COMMANDER_OPTION_NOLINT = !!commander.nolint; } if (commander.config) { process.env.COMMANDER_OPTION_SFX_FILE = commander.config; } }复制代码
文件第一行代码 #!/usr/bin/env node"use strict"
是为了告诉系统,指定使用node执行该脚本文件。
在开始阅读上述源码之前,首先我要了解一个工具( commander ),它是node.js命令行界面的完整解决方案,可以在通过解析sfx2后面的命令执行不同操作(函数),并且可以给对应命令设置参数、参数的简写和命令描述等等。
在了解commander的使用方法之后,就可以知道脚手架到底是如何执行的了。
3、template/init
- 流程图
- 依赖模块
- run函数(每个命令都有一个对应的run函数作为该命令的入口函数)
- 首先执行的是 checkEnv 方法:
- 从注释可以看出该方法是用来检测当前目录是否为空,使用node的fs.readdirSync同步获取一个包含“指定目录下所有文件名称”的数组对象,再通过process.cwd获取Node.js 进程的当前工作目录。如果当前目录下不为空,则给出提示并退出进程process.exit(1)。
- 执行完checkEnv方法之后,进入异步控制流程,执行 stdIn 函数
- 上述代码中的 prompt 方法是通过 process.stdout 和 process. stdin 实现询问框的输出和输入
- 通过用户的输入项目的基本信息之后,返回项目基本配置信息,根据配置信息中的模板名称,开始拷贝 template 目录下的模板目录文件
- src为sfx项目中初始化的模板项目文件路径
- dist为当前sfx运行在哪个工作目录,也就是我们要将模板文件copy到的指定路径
- option为之前用户填入的项目信息
执行 _copy 方法
- existsSync:以同步的方法检测目录是否存在
- statSync(src).isDirectory : 以同步的方式判断当前路径是否是一个目录
- mkdirSync:以同步的方式创建目录
- readdirSync: 返回一个包含“指定目录下所有文件名称”的数组对象
- writeFileSync: 以同步的方式将数据写入文件,文件已存在的情况下,原内容将被替换
这个方法先后做以下几件事情:
- 判断模板源目录是否存在,不存在则给对应信息,并退出当前进程,此处应将return修改为process.exit(1),否则后续的run主函数中的代码依旧会执行。
- 使用递归来遍历源目录来实现整个源目录拷贝到目标路径
4、webpack/dev/index (后续更新)
5、webpack/third_part/index (后续更新)
6、webpack/prod/index (后续更新)
7、eslint/lint (后续更新)
8、webpack/test/index (后续更新)
六、对比其他脚手架
对比omi-cli,sfx可以直接接入已有的项目来给项目带来开发环境和生产环境的收益,而omi-cli则是注重的是为开发者更快的进入一个新项目的开发。
七、可优化的部分
1、在 webpack.config.base.js 中的多进程压缩配置 UglifyJsPlugin中的 parallel选项设置为true默认开启 require('os').cpus().length - 1
个并发数来进行压缩,可将其设置为 require('os').cpus().length
来增加一个并发数来提高压缩的效率。
2、 sfx init 的流程可以做优化
- 这个流程减少了用户自己去创建文件夹并进入文件夹的操作
- 在指定模板的时候应该根据已有的模板去给用户选择,如下图:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
XMPP
Peter Saint-Andre、Kevin Smith、Remko TronCon / O'Reilly Media / 2009-5-4 / USD 39.99
This practical book provides everything you need to know about the Extensible Messaging and Presence Protocol (XMPP). This open technology for real-time communication is used in many diverse applicati......一起来看看 《XMPP》 这本书的介绍吧!