webpack-papa-script 是一个帮助前端开发更轻易地执行从开发到部署的工程化工具。
它是一个前端多页面、多个项目集成的解决方案, 方便部署大量短期性的页面, 例如大量的活动推广专题。方便集成共用组件, 快速部署, 免去频繁执行依赖包的安装。
- 基于 webpack v3, 贴紧webpack的生态
- 蜂巢式的大项目,可任意创建小项目
- 可单独或批量编译小项目,无需整个蜂巢项目进行编译
- 批量编译时,自动识别所有项目
- 自动识别页面入口
- 相互独立的小项目可共用组件代码
- 小项目可注入非 webpack 的公共代码包的引用, 公共包更新即对整个蜂巢项目起效
- 便捷的业务环境切换
- 灵活的项目部署配置
- 灵活的开发环境
- 快速部署
- 可通过模版创建小项目
- 集成了自动上传 ftp
安装一个工具包
npm i create-webpack-papa -g
创建一个命名为 "proj-hive" 的站点项目
create-webpack-papa create proj-hive
等待依赖文件自动安装完成后, 进入项目, 即可使用 webpack-papa-script 的各种功能。
cd proj-hive
创建一个命名为 "my-proj1" 单页初始小项目
npm run create my-proj1
对 my-proj1 进行本地开发
npm run watch my-proj1
部署上线代码
npm run build my-proj1
其他功能参考详细的cli命令
通用地, 命令采用npm run foo
模式, foo代表具体的命令名称, 后面可接其他参数。一些参数内容是与papa.config.js
的配置对应的。
功能 | 例子 | 说明 |
---|---|---|
创建新小项目 | npm run create foo |
创建一个foo小项目。支持层级的目录:假如是src/2018/abc , 则把foo 换为2018/abc |
选用特定的模版创建项目 | npm run create foo t min |
模版使用src/_template_min (默认模版是_template_def ) |
开启本地开发 | npm run watch path/to/foo |
path/to/foo 代表src/ 下的文件夹 |
生成部署代码 | npm run build path/to/foo 或npm run build path/to/foo,path/to/bar |
编译目录path/to/foo 的代码; 可以指定多个位置, 以, 间隔。默认是部署config.productEnvType 指定的模式编译; 具体的编译效果请看具体介绍。 |
批量生成部署代码 | npm run build-all |
自动查找src/ 下的所有的项目, 依次自动编译所有。 |
设置批量生成的范围 | npm run build-all scope path/to/foo |
自动查找src/path/to/foo 下的所有的项目, 依次自动编译所有;同build 命令, scope 的值也可以包含多个, 以, 间隔。 |
选择部署环境 | npm run build foo test |
pro 是config.deployEnvType 中定义的key值, 默认是test|pre|pro . |
上传ftp | npm run build foo test u 或npm start u foo |
foo 在test模式编译后, 立即上传; 或指定上传的项目。(上传目录在config.localAssetPath 设定。) |
生成公共资源 | npm run deploy-static |
把./resource/js 打包📦, 生成脱离webpack的公共代码包到./resource/bundle , 并把./resource/bundle 的所有资源分发到config.deployEnvType 配置的目录上(watch命令也会自动执行资源分发) |
设置前端的环境变量 | npm run watch foo mode pro |
把本地开发的代码的环境切换为pro的环境。为了避免误操作, 只有在config.developEnvType 设置的环境下才能使用mode (watch 即为该环境)。假如确实需要在其他环境切换, 可以把mode 换为hard-mode |
强制编译环境为production | npm run watch foo p |
一般不需要使用, 某些情况为了调试或测试可用。此例子可把本地开发的编译效果改为像build那样 |
强制编译环境为development | npm run build foo d |
一般不需要使用, 某些情况为了调试或测试可用。效果与p 相反。 |
webpack-papa-script 有非常灵活的编译功能, build的操作可以针对一个或多个或一个范围内的所有项目, 详见使用build命令。
此外我们可以在项目下的papa.config.js
自定义build的各项配置, 如:
先假设一个papa项目上有以下这样的目录结构, 它含有三个小项目foo
,bar/baz
,bar/qux
:
|-build
|-dist
|-node_modules
|-resource
|-src
|---foo
|-----index.js
|-----index.html
|---bar
|-----baz
|-------index.js
|-------index.html
|-----qux
|-------index.js
|-------index.html
npm run build
后面接需要build的目标即可, 目标是一个基于项目目录src
下的子目录, 如:
-
npm run build foo
代表编译src/foo
这个项目, -
npm run build bar/baz
代表src/bar/baz
这个项目。
目标可以设置为多个, 一个命令build多个项目。用,
隔开:
npm run build foo,bar/baz
同上面两条命令分别执行的效果。
目标可以设置为一个范围内的所有小项目, 如:
npm run build-all
代表编译src
下的所有项目。npm run build-all scope bar
代表编译src/bar
下的所有项目, 按照以上的例子, 则是bar/baz
和bar/qux
。
同样, 指定的范围也可以包含多个, 用,
隔开。(注意例子中的foo
本身是一个项目, 用scope
是不能识别的)
编译模式分了两个概念, 一个是前端环境, 一个是编译模式。我们可以通过papa.config.js
定义这些配置, 然后在命令行可以使用配置里所定义的名称。
我们上线一个项目前通常需要经过几个过程, 如本地开发环境, 测试环境, 预发布环境等。我们通过papa.config.js
定义这些前端环境, 并且将这些环境变量注入到前端代码里。相关的属性例子如下:
deployEnvType
key值定义有哪些环境, 值为编译的输出目录。如以下定义了 test|pre|pro
3个。
deployEnvType:{
test: 'build/activity',
pre: 'dist/pre',
pro: 'dist/pro'
}
deployEnvMapFetch
可以定义环境对应的前端环境变量, 该值会注入编译后的代码里。
deployEnvMapFetch: {
test: 'test',
pre: 'pre',
pro: 'produce'
},
frontendConfCode
就是注入的js代码模版, 它接收两个变量, 其中一个是mode
即为前端环境变量。详细例子参考使用方法生成的papa.config.js
例子。
有了这些配置后, 我们就可以在命令中指定我们要使用哪一个环境:
npm run build foo [test|pre|pro]
假如我们不指定环境的话, 它会自动选取 papa.config.js 的productEnvType
定义的环境。
固定了两种:开发模式和发布模式
我们可以用developEnvType
定义上文中deployEnvType
里的哪个前端环境为开发模式, 其它则自动作为发布模式:
developEnvType:{
deploy: 'test',
fetch: 'test'
}
deploy
是环境的命名, 需要是deployEnvType
中的一个key值;
fetch
是前端环境变量, 跑watch
命令时会使用这个值, 一般上与deployEnvMapFetch
对应的一致即可。
运行watch
时, webpack的devtool
是#inline-source-map
。
运行build
时, 假如前端环境等于developEnvType
的定义, 则devtool
为eval
。其他情况则不声明 devtool。
当然可以通过papa.config.js
的webpackConfig
强制改写webpack配置属性。
编译输出位置即上文的前端环境提到的deployEnvType
中定义。
项目识别的参考值定义在 papa.config.js
, 当目录中含有projContainsOneOf
的值, 就识别为项目, 其中, 假如该目录拥有proj.json
, 则识别为一个多页项目, 其它情况就是单页项目。
具体介绍如下:
参数名 | 默认值 | 描述 |
---|---|---|
projContainsOneOf | ['m', 'pc', 'proj.json', 'config.json'] |
辨别一个项目时, 只要一个文件夹里面包含此属性定义的文件或文件夹名, 则认定它为一个项目。(无论单独页面还是多页面) |
projScanExclude | ['modules','components', 'img', 'js'] |
获取所有项目时, 排除以下这些文件夹里面的内容 |
entryInclude | ['index.js', 'index.html'] |
入口文件夹必须包含这些文件。一般不要更改这个值, 其中包含 index.js, webpack才能正常编译 |
commSingleProjSubPage | ['m', 'pc'] |
可以定义通用的, 每个小项目可以默认包含这些子页面, 它们共享config.json , 无需每个这种页面都添加 多页项目识别文件proj.json 。 比如一个单页项目, 不适合做响应式, 需要包含电脑端和移动端两个页面。可以定义为空, 则忽略掉这个情况。具体请看默认的初始化项目中的模版项目。 |
公共静态资源存放在resource/bundle
文件夹中, js资源放在resource/js
。
运行npm run deploy-static
部署后, 会按照config.staticFileConcatOrder
, 在js
文件夹内查找到, 合并压缩至resource/bundle
, 并把所有bundle资源分发到config.deployEnvType
定义的所有文件夹。
运行npm run watch
时, 会自动把bundle复制到config.developEnvType
对应的文件夹, 无需手动运行npm run deploy-static
。
合并的 js 资源将会在所有编译后的页面的html中插入引用。
通过papa.config.js
配置文件, 可自定义各项部署功能, 比如可定义部署的环境, 输出路径, 让生产代码是否兼容ie8, 或改写webpack配置。详见项目配置
避开了 webpack 的单项目的特性, 只需按照简单的约定规则新建一个页面目录, 即自动识别为一个 webpack 编译的入口。
如上文定义如何识别项目所介绍, 一个目录含有proj.json
就识别为一个多页项目, 否则假如符合projContainsOneOf
的范围的话, 则识别为一个单页项目。
proj.json
文件只作为标记,内容可为空。
多页项目里, 程序将寻找所有符合entryInclude
的识别为页面;单页项目假如含有commSingleProjSubPage
定义的子页面的话, 则自动识别为拥有子页面, 参考html风格
每个页面的html文件只需包含业务代码, 编译后会将内容传入html模版。
每个页面都包含一个config.json
文件, 用于定义html模版的变量, 如:
|-foo
|---config.json
|---index.js
|---index.html
假如定义了commSingleProjSubPage
, 而且项目中含有这些子页面, 则json文件放在子页面文件夹的同级, 假如该值是 ['m','pc']
, 则如下:
|-foo
|---config.json
|---m
|-----index.js
|-----index.html
|---pc
|-----index.js
|-----index.html
默认的, 用npm run create
命令创建的模版项目是以上第二个的目录结构。
config.json
有如下示例:
属性 | 默认值 | 描述 |
---|---|---|
title | 标题 |
html的<title/> |
htmlFile | index.html |
指定html文件的名字 |
templateName_xx | index_xx.handlebars |
指定html模版文件。xx 是假如此页面包含 commSingleProjSubPage 定义的子页面, xx就是该子页面名字。 |
templateName_comm | index_comm.handlebars |
假如该页面不包含commSingleProjSubPage 的子页面, 则使用这个来指定模版 |
bodyProp | "" |
<body/> 的属性 比如"class=\"top\" data-version=\"3.0.0\"" |
moreMeta | "" |
<head/> 内添加内容。如"<link rel=\"dns-prefetch\" href=\"https://www.github.com\">" |
模版文件位于resource/html
。
如果项目目录不包含 config.json, 将不会使用html模版
, 而会把目录顶层的index.html
视为完整的 html 文件(webpack 编译出来的资源会自动填入)
每个小项目互相独立, 但可以共用模块。两种实现方式:
- 共用组件
所有小项目都可以 require 共用组件。因为是独立编译, 项目之间不会互相影响, 组件更新后, 各个项目需重新编译才会生效。这可避免在更新组件后, 因项目数量庞大, 意外地应用在不应生效的其他项目而导致意外的错误, 从而降低更新代码的负担。
- 公共代码包
所有小项目都会引用共代码包, 此包有修改, 所有已在线上的项目都能获取到更新的公共包, 无需重新编译。详见部署非webpack的公共静态资源
可在编译时在项目的业务代码里设置、切换不同的内容, 让开发过程更为灵活。比如可应用于部署环境的切换及fetch数据的环境切换;
本地开发采用 webpack-dev-server, 默认开启了热更新功能, 并且整合了更灵活的代理功能。
代理功能可将本地开发打通其他项目环境, 比如可共用cookie等缓存。 可以正则匹配路径。
在一些场景中, 比如我们这个项目并非一个完全独立的站点, 它还需要与其他项目联动, 那么代理功能就非常实用了。我们可以通过papa.config.js
的 proxy
设置代理, 可以通过正则匹配路径实现代理, 并可以设置多个匹配规则。
如以下配置, 代理了路径以非 "activity" 开头的所有请求 到本地的80端口:
proxy:[
{
filterPathname: /^\/(?!activity\/)/,
target: 'http://localhost:80',
},
],
webpack-papa-script 基于webpack, 已集成所有常见资源的处理, 以下是集成功能的列表:
-
ES6 -> ES5
-
React
-
集成babel-runtime, Promise随便用
-
可开启ie8兼容, 把代码转为ES3
在
resource/html/index_pc.handlebars
模版里, 加了ie8以下 es5-slim和 sham 文件的引用, 我们可以按照实际场景把该引用链接更换。 该文件在resource/bundle/
中已预置, 执行npm run deploy-static
即可把es5-slim-sham文件复制到部署目录中。
-
handlebar
-
sass-postcss-autoprofixer
-
webpack-dev-server 本地开发集成热更新及代理
-
extract-text-webpack-plugin build时独立出css文件
- js[x]
- [s]css
- png|jpg|gif|svg
- handlebars|tmpl
- woff|woff2|eot|ttf
-
sw.js
使用 file-loader 编译, require后就会把该文件独立释出到输出目录;输出文件名带hash值, 可确保sw更新被触发。
-
manifest.json
同样使用 file-loader 编译, 但不带hash值, 便于固定写在html模版。
-
*.iso.(png|gif)
绝对不合并到css文件。其他的小于 4KB 的图片将转为 base64 合并入css文件
以下内容因有不理想的效果, 所以没有支持:
-
图片压缩功能
转换png时, 现有的图片压缩插件是把png24转为png8, 会导致图片低质量, 所以在找到更好的压缩功能后再添加该功能。
-
.html
文件内的资源引用如index.html假如有
<img src="foo.jpg"/>
, foo.jpg无法解析。这类功能请转移到css、React 或 handlebars实现。
假如 webpack-papa-script 满足不了项目的需要, 随时可运行 create-webpack-papa eject
, 把 webpack-papa-script 的代码转移到项目目录上, 对构建代码自行修改。不过需要注意的是, 该操作无法逆转, 操作前请确认清楚。
以下是默认配置, 我们可以在项目的 papa.config.js 修改这些属性:
{
// 自动上传ftp需要的配置信息
ftp: {
host: '192.168.1.1',
port: '',
user: 'user',
password: 'ps'
},
// ftp上的根目录
remoteBasePath: '',
// ftp的目录,{$target}是一个固定的变量名,是小项目名称的占位符
remotePath: '/activity/{$target}/',
// 需要上传到ftp的本地目录
localAssetPath: 'build/activity',
// 部署上线时的host
domainName: 'http://m.okpapa.com',
// js,css,image 等资源的host
cdnDomain: 'https://images.okpapa.com',
// 代理信息, 数据类型: object array
proxy:[
{
filterPathname: /^\/(?!activity\/)/, // 代理 pathname 以非 activity开头的所有请求
target: 'http://localhost:80',
},
],
//本地开发环境的服务端口
servePort: 3005,
// 本地开发环境的静态资源基目录(同 devServer.contentBase)
serveContentBase:'./build/',
// 定义`resource/js` 中的js文件的合并顺序, 合并生成脱离webpack的公共代码包
staticFileConcatOrder: [],
// 合并js的命名
staticFileName:'common.js',
// 非模块的公共文件的输出子路径
staticFileSubPath:'static',
// 是否启用自带的plugin
defPlugin:{
sri: true, //webpack-subresource-integrity, 非https的话,请设为false
uglifyJs: true //是否压缩js
},
// 加入其他 webpack 插件
customPlugin:{
production: [], // 正式环境
development: [] // 开发环境
},
/**
* 即将执行编译前的时候,根据这个函数返回决定是继续还是中断。可为空,空则忽略。分正式环境和开发环境
* aTargets {array} 用户输入所有项目的完整路径
* return {boolean} false 则中断编译
*/
shouldCompileProceed: {
production: (aTargets)=>{return true},
development: (aTargets)=>{return true},
},
// 覆盖预置的webpack配置
webpackConfig: {},
// 是否支持ie8
kiss_ie8: true,
// 定义一个页面实际可包含有哪些版本的页面。它们虽然是不同的页面,但是业务内容是一样的,并且共用一个“config.json”。
// 比如一个单页项目, 不适合做响应式, 需要包含电脑端和移动端两个页面。
// 可以定义为空, 则忽略掉这个情况
commSingleProjSubPage:['m', 'pc'],
// 辨别一个文件夹是否为一个项目时, 只要该文件夹里面包含以下其一文件或文件夹, 则认定它为一个项目。(无论单独页面还是多页面)
projContainsOneOf: ['m', 'pc', 'proj.json', 'config.json'],
// 检索所有项目时, 排除以下这些文件夹里面的内容(不会在已识别为proj的文件夹里再查找)
projScanExclude:['modules', 'module', 'static', 'components', 'component', 'img', 'js'],
// 验证 webpack 入口必须包含这个值的所有文件。
entryInclude: ['index.js', 'index.html'],
//本地开发时 使用哪个环境(deploy),和前端代码注入的环境变量名(fetch)。
developEnvType: {
deploy: 'test', //命名
fetch: 'test' //环境变量名
},
//正式上线的环境
productEnvType: {
deploy: 'pro',
fetch: 'produce'
},
//环境名称对应的输出路径
deployEnvType: {
pre: 'dist/pre',
pro: 'dist/pro',
test: 'build/activity'
},
//环境名称默认对应的前端环境变量名
deployEnvMapFetch: {
pre: 'pre',
pro: 'produce',
test: 'test'
},
// 环境名称的中文名, 用于命令行的显示,以便识别当先运行的命令是在做什么
releaseEnvDesc: {
test: '开发环境🤔',
pre: '预发环境😛',
pro: '生产环境😝'
},
// 前端环境的中文名
fetchEnvDesc: {
test: '测试环境🥝',
pre: '预发环境🥑',
produce: '生产环境🍓'
},
// 注入前端代码的内容, 它将会被浏览器执行。 "{$xxx}"为变量的占位符
// mode: 前端环境变量
// debug: 环境与productEnvType一致时, 则为false, 否则true
frontendConfCode:`try{
Object.assign(window.publicConfig, {
mode:"{$mode}",
debug:{$debug}
});
}catch(e){}`,
},