阅读 2073

记一次企业级脚手架搭建经历

企业级脚手架搭建

起因我是一位阿里的大佬给了一段问题,说搭建一个企业级脚手架,虽然我之前也写过一些脚手架搭建的内容,但是没有一个完全的整合以及发布到npm的一个实操版本,只是给我自己的一些学习内容进行了记录,所以,当我看到这些问题的时候,我第一个反应是将我所学的内容进行一个整合并优化

脚手架思维导图

这也是我的个人一个习惯,在开始写东西之前,一定要先想下,这个写的内容具体包含了什么然后针对性的去展开,虽然这也是要求之一,但是完全符合我个人的开发习惯

我将整个脚手架内容分成了5个部分,其实也可以算是4块主要的内容,因为argv_store是自己写的一个管理cli参数的一个库

  • argv_store
  • webpack配置
  • 项目代码
  • cli工具
  • deployer 一键部署能力

argv_store

他是一个管理我们在cli界面上输入的参数, 例如:

mj-cli create --rename test

当使用mj-cli的时候使用的argv_store会获取到的你的参数以及对应的内容,即:


// command参数就是create
// 获取到的options参数
{
    '--rename': 'test'
}

复制代码

内容很简单,甚至说没有什么可以表达很优秀的地方,但是这就是我想要的一种效果

相比较社区的通用性方案commander,我写的这个库在复杂性上暂时还没法与其相比,但是我为什么不使用commander而是自己重新写了一个这样的处理工具,因为我想要将代码的可读性达到更高,能够一眼就识别出,你这个方案运行的方法是什么,让下一个人直接能够看出来

在这一层次,对比一下我们的实现方法:


// commander
program
  .command('setup [env]')
  .description('run setup commands for all envs')
  .option("-s, --setup_mode [mode]", "Which setup mode to use")
  .action(callback);

program
  .command('exec <cmd>')
  .description('execute the given remote cmd')
  .option("-e, --exec_mode <mode>", "Which exec mode to use")
  .action(callback)

program.parse();

// argv_store
program
    .command('exec', 'execute the given remote cmd', callback)
    .option('-e, --exec', 'Which exec mode to use')
    .command('setup','run setup commands for all envs', callback)
    .option('-s, --set', 'Which setup mode to use')
    .parse()

// 当然这两种方式都是可以互通的不管是argv_store还是commander,都可以支持这两种写法
复制代码

我承认我在一开始是准备使用commander的,但是当我看到这种使用方式以及方法的时候,我感觉我个人的灵魂不太想用(纯粹是个人原因),然后我就大部分借鉴commander的使用方式,自己编写了一个argv_store的库,从复杂度上讲,还比不上commander,但是从可读性上来说,我觉得我这个能够看得更加清晰明了

具体的argv_store的使用方法我就不详细阐述,在github上都有写,虽然也是很简单(狗头自保)

webpack配置

其实我最开始入手写的应该是这块内容,但是我为什么我会将argv_store先讲呢,因为他其实是游离在脚手架之外的内容,并不属于脚手架,毕竟只是我个人的习惯而且做的一个改变

在开始写的时候,就一直在回忆,我之前学的内容具体有什么,有什么可以体现在这个配置上面的,然后根据我的区分大致上分为了以下几类:

  • 基本配置
  • 缓存处理
  • 环境区分
  • 打包优化
  • 可外部配置
  • script命令

基本配置

是的,基本配置,就是我们写webpack的时候必须要写的一些内容,以下会简单介绍下略有区别的内容

entry

针对入口文件,我思考了下,决定选择单页以及多页都可以运行的模式,这就需要与业务代码进行深层次的耦合

在入口文件获取的时候,首先会判断当然业务代码的src目录下是不是会有index.js文件,如果存在就会使用单页面的构建方式去进行构建,当找不到index.js文件的时候,会去找src/view下的每个文件夹里的index.jsx文件,将每个文件夹的名称作为html文件的name


if (fs.existsSync(signalPath)) {
    entryObj.index = signalPath;
} else {
    const data = fs.readdirSync(viewPath);
    data.map(name => {
        const dirPath = `${viewPath}/${name}`;
        const stats = fs.statSync(dirPath);
        if (stats.isDirectory() && fs.existsSync(`${dirPath}/index.jsx`)) {
            entryObj[name] = `${dirPath}/index.jsx`;
        }
    })
}

Object.keys(entryObj).map(key => htmlPlugin.push(new htmlWebpackPlugin({
    template: `${DIR}/template/index.tpl.ejs`,
    filename: `${key}.html`,
    minify: true,
    inject: true,
    collapseWhitespace: true
})));

复制代码

optimization

在选择压缩的时候,会考虑到开发环境以及生产环境,所以在开发环境的时候就不进行压缩,在生产环境中才会去压缩js跟css

module

module里除了正常的针对js,css,img,front等内容进行编译以外,针对less/sass,这边特意做了一些支持,一个项目里面同时支持module以及非module形式的内容,使用oneof来只使用其中一个内容

缓存处理

这个涉及到持久化缓存的内容,当我们使用呢webpack打包的时候会使用hash作为标识,如果hash经常性变化的话,那么对于我们来说强缓存的策略就会不断的更新,所以为了解决这个问题,webapck4提供了一个新的能力contenthash,以及moduleId:hash

当我们设置这两个内容的时候,每次打包的内容会减少变化,只有文件修改过了,才会产生新的hash值,因为contenthash是针对文件内容的,既然已经有了这个contenthash为什么还需要修改moduleId,因为在每次打包的时候会有一个默认的文件顺序,如果新增或者删除了也会导致命名变化,所以需要将文件的id命名也改成hash才能成功将添加删除也包含在内

环境区分

将webpack的打包配置分成了3个层次内容

  1. development
  2. production
  3. preProduction

设置webpack.config.js文件默认导出一个function,参数为dev(即上述的环境变量),根据不同的环境设置不同的文件内容然后通过build或者dev命令的区别进行对应的配置获取,最终能实现的是多个内容的合并,比如是analyz的方式,获取对应的包信息

打包优化

这个是一个老生常谈的事情,在webpack4里面已经是很足够的表现出开包即用的能力,只需要简单的配置就能够完成打包,常用的优化有那么几个

  1. 使用TerserPlugin来进行js的压缩
  2. 使用happypack来进行js的loader转换
  3. 使用dllPlugin来通过提前打包文件形成依赖包,加快打包时间
  4. 开启打包缓存,能够提升二次打包速率

可外部配置

通过process.cwd方法能够获取当前文件夹所在目录,然后在根目录中使用config.js来配置对应的需要修改的文件内容,使用webpack-merge来整合两个对应的配置文件,这里有几种方法,一种是通过环境变量来控制文件的内容,另一种是分成多份文件,来根据不同情况配置不同的webpack配置文件

script命令

这个就比较简单了,只需要在packagejson文件中,添加bin字段,然后写入需要执行的文件,就可以直接通过命令行执行对应的npm包了,在下载的时候默认会将写了bin字段的文件放入对应node_module/.bin目录下面,是局部可执行,如果是全局下载那就会是全局可执行文件

dev 与 build

开发环境与生成环境打包

add

添加一个新的页面,避免做重复性工作

deploy

直接部署进对应的服务器上,并执行一键部署的能力

一开始我的脚手架其实是没有添加这一块内容,后来我对整个项目进行回溯的时候发现如果有一个能够自动化部署的方法会比较有效,所以我加入了npm run deploy命令,当前只拥有一个环境部署的能力,没有扩展多余环境部署,适用于单人应用于开发环境的打包(后续会持续更新,目标是直接贯穿整个开发测试以及上线的工程线)

项目代码

在我做的这个项目里面,使用了一些常见的react库,包含了react-router, redux, redux-thunk,主要还是针对单页面设置,没有考虑多页面的情况,多页面的情况可能需要各位下载之后自行的处理一下,根据上面的步骤里面来自己实现

cli工具

这个其实跟webpack的script命令一样,都是一个脚手架命令,只是帮别人省去了很多事情,通过node去下载对应的git包,然后根据配置删除不需要的文件达到开箱即用的效果

总结

完整的github地址

mj_script

mj_react

argv_store

mj-cli

做完这个,我感觉对webpack以及node的掌控能力提升了不少,但是总体来说,针对react的项目代码编写还存在一些问题,没有很好的对文件进行分离,以及整体的架构内容还存在的一定程度上的欠缺,甚至说,在考虑取舍方面上没有做到最优,什么都想要,但是要了之后导致代码块显得比较混乱,接下来准备将文件更加细分的调整下,以及webpack的命令和他的git包等内容需要进行更加有意义的区分