**Pepper**

pepper 是一个基于 webpack 的打包工具，不依赖于具体项目，相反，它是诸多 RWE ( React & Webpack & ES6 ) 项目共同点的抽离。对 RWE 项目来说，开发环境和部署环境都是通用的，他们存在的唯一目的，就是方便代码的调试和打包。这就是 pepper 诞生的初衷。

现在，只需在RWE 项目的根目录下创建 `pepepr.config.js`（ pepper 的配置文件），就可具备初始 RWE 项目的所有功能。

### 关于 Yarn

pepper 可以通过 [yarn](https://github.com/yarnpkg/yarn) 进行安装

```
// firstly: install yarn, may require your sudo privilege
npm install yarn -g;

// then install depends packages
yarn install;

// link and compile pepper
npm link;

// then you are done! enjoy pepper ;)
```

- [如何安装](#install)
- [项目创建](#create)
- [项目打包](#build)
- [项目测试](#test)
- [配置文件](#config)
- [API](#api)
- [常见问题](#questions)
- [关于 npm](#npm)

### <span id="install">如何安装</span>

- gitlab 手动编译

  ```
  # 克隆 pepper 仓库到本地目录（默认 pepper 即可）
  git clone git@gitlab.intra.wepiao.com:FEI/pepper.git

  # 进入 pepper 目录
  cd pepper

  # 安装运行时依赖 & 编译
  npm link

  ```
- npm 全局 ( depreacted & outdated) *此种方式后续会逐渐废弃*

  ```
  npm install we-pepper -g --registery http://npm.intra.wepiao.com
  ```
### <span id="create">项目创建</span>

| cli 命令                      | 说明
|-------------------------------|----------------------------
| pepper init [name]            | es6 & react-router
| pepper init-redux [name]      | es6 & react-router & redux
| pepper init-rn [name]         | react-native (演出票 DEMO)

目录结构一览

```
.
├── /dist/                              # 代码打包目录
├── /node_modules/                      # node依赖包
├── /src/                               # 源码目录
│   ├── /pages/                         # 页面
│   ├── /components/                    # 公用组件
│   ├── /assets/                        # 图片、字体资源
│   ├── /scss/                          # 公用样式
│   └── /utils/                         # 公用JS模块
├── pepper.config.js                    # pepper的配置文件
├── index.template.html                 # 首页HTML模版, 可选
└── package.json                        # 这个就不用说了吧
```

### <span id="build">项目打包</span>

| cli 命令                | 说明
|-------------------------|-----------------------------------------------
| pepper start [-p PORT]  | 本地调试，基于 start 配置
| pepper test             | 运行测试代码, 基于 test 配置打包
| pepper pre              | 基于 pre 配置打包，压缩
| pepper release          | 基于 release 配置打包，压缩，移除 console 日志
| pepper library [mode]   | 基于现有配置文件，将代码打包成 lib 库，可发布至 npm ，供其他项目使用, [使用参考](#library)

打包结果一览

```
➜  pepper pre
Hash: d72ff25390f520660578
Version: webpack 1.12.11
Time: 8205ms
                Asset       Size  Chunks             Chunk Names
js/shared-9f5500d8.js    34.7 kB    0, 3  [emitted]  shared
  js/home-1fb4ec5e.js    6.85 kB    1, 3  [emitted]  home
js/vendor-5a9e0e26.js     131 kB    2, 3  [emitted]  vendor
           index.html  647 bytes          [emitted]
   [0] multi vendor 40 bytes {2} [built]
    + 194 hidden modules
```

### <span id="test">[项目测试](http://silvenon.com/testing-react-and-redux/)</span>

| 基于模块                                        | 说明
|-------------------------------------------------|----------------------------------------
| [ava](https://github.com/avajs/ava)             | 🚀 Futuristic JavaScript test runner
| [enzyme](https://github.com/airbnb/enzyme)      | JavaScript Testing utilities for React

> *测试功能正在开发中*

### <span id="config">配置文件</span>

| 配置项                  | 默认值       						| 说明
|-------------------------|--------------						|-------------------------------------------------
| host                    |  0.0.0.0     						| 调试IP，本地调试使用默认值即可
| port                    |  9527        						| 端口，start 模式下，可通过 `-p [PORT] 覆盖该项`
| base                    |  src         						| 代码根目录
| externals               |  {}         						| demo { '$': 'jQuery' }
| jshash                  |  true         						| 是否启用 js 文件名字添加 hash 后缀 
| build                   |  dist        						| 打包目录，打包后的代码将放置到此处，与 base 同级
| static                  |  [见示例配置](#config_default)   	| 静态资源 CDN 域名设置，**路径结尾包含`/`** 代码中以 `STATIC` 引用, etc.. `console.log(STATIC)`
| api                     |  [见示例配置](#config_default)   	| API 路径设置，**路径结尾不包含`/`** 代码中以 `API` 引用
| globals                 |  [见示例配置](#config_default)      | 更多全局变量设置，js代码中以配置项 key 的大写 `KEY` 形式引用，如果在首页模板中，用法则为{%=o.configs.KEY%}
| vendor                  |  ['react', 'react-dom'] | 将指定的类库打包到 **vendor-[hash].js**中，建议常用的类库放到此处。此项不包含业务代码，不会经常变动此项，可充分利用缓存优势
| alias                   |  [见示例配置](#config_default)	    | 1) 路径别名 2) [模块化引用方式，去相对路径痛点](http://gitlab.intra.wepiao.com/FEI/talks/tree/master/kick_ass_relative_path)
| alias_modules           |  [见示例配置](#config_default)	    | 模块别名 , 比如使用 [react-lite](https://github.com/Lucifier129/react-lite) 替换 [react](http://facebook.github.io/react/)
| devtool                 |  source-map     					| [参见 webpack 调试选项](http://webpack.github.io/docs/configuration.html#devtool)
| template                |  [见示例配置](#config_default)		| [html 模版配置](#html_template)
| template.title:         |  									| 模版 `document.title`
| template.keywords       |  									| 模版 `meta keywords`, 默认为空
| template.description    |  									| 模版 `meta description`, 默认为空
| template.viewport       |  width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no | 模版 `meta viewport` | viewport 配置
| template.favicon        |  									| 模版 `favicon`, 默认为空
| template.path:          |   									| 设定自定义模版的的路径，会替换默认模版,可选。 有语法要求
| pages                   |  pages         						| 程序入口，页面目录，默认为 `pages/index`，可修改, 支持多 html 模版，详细见示例配置
| scss                    |  scss         						| 公共样式目录，可修改
| assets                  |  assets         					| 静态资源目录，可修改
| components              |  components         				| 页面间共享的组件目录，可修改（非共用组件，放置在当前页面目录即可）
| eslint                  |  false         						| 是否启用 eslint , 默认关闭，如果启用该选项，请在项目根目录提供自己的 .eslintrc
| css_modules             |  false         					    | 是否启用 [CSS Modules](https://github.com/css-modules/css-modules), 默认关闭
| esmode                  |  es6         						| 模版代码风格，支持 `es5` 和 `es6` , 默认为 `es6`, 兼容 `es7
| transfer_assets         |  false         					    | 打包时是否 copy `asset` 目录，默认 false
| base64_image_limit      |  1024                				| 对所有小于该大小的图片启用 base64 转码, 默认 10240 (10k)
| transfer_assets         |  false         					    | 打包时是否 copy `asset` 目录，默认 false


<br/> <b id="config_default">示例配置</b>

```
{
    "host": "0.0.0.0",
    "port": "9527",
    "base": "src",
    "build": "dist",
    "static": { // CDN domain, or just leave it blank if not using
        "start": "/",
        "test": "/",
        "pre": "/",
        "release": "/"
    },
    "globals": {  // 配置全局变量，这里配置了 static_api 和 api 两个全局变量
        // static api
        "static_api": {
            "start": "", // local api base entry
            "test": "",
            "pre": "", // online api base entry
            "release": ""
        },
        "api": {
          "start": "",
          "test": "",
          "pre": "",
          "release": ""
      }
    },
    "vendor": ["react", "react-dom"],
    "alias": {
        "wepiao": "components",
        "scss": "scss"
    },
    "alias_modules": {
        "react": "react-lite",
        "react-dom": "react-lite"
    },
    "devtool": "source-map",
    "template": {
        "path": "index.template.html", //使用自定义模版
        "title": "",
        "keywords": "",
        "description": "",
        "viewport": "",
        "favicon": "assets/images/favicon.ico"
    },
    "pages": "pages",
    /*  // multiple template config
     *
     *  pages: {
     *      index: { entry: 'pages/index', template: 'index.template.html' },
     *      other: { entry: 'pages/other', template: 'other.template.html' }
     *  }
     */
    "scss": "scss",
    "components": "components",
    "assets": "assets",
    "eslint": false, // 如果启用该选项，请在项目根目录提供自己的 `.eslintrc`
    "css_modules": false,
    "transfer_assets": false,
    "base64_image_limit": 10240,
    "esmode": 'es6' // swith esmode on template creating, es5/es6, default to es6
}
```
<b id="html_template">默认HTML模版</b>

```
<!DOCTYPE html>
<html>
  <head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
	<meta name="keywords" content="webpack, react, es6, build tools"/>
	<meta name="description" content="webpack auto build tools intergated with one command"/>
	<meta name="apple-mobile-web-app-capable" content="yes"/>
	<meta name="format-detection" content="telephone=no"/>
	{% if (o.htmlWebpackPlugin.files.favicon) { %}
	<link rel="shortcut icon" href="{%=o.htmlWebpackPlugin.files.favicon%}">
	{% } %}
	{% for (var css in o.htmlWebpackPlugin.files.css) { %}
	<link href="{%=o.htmlWebpackPlugin.files.css[css] %}" rel="stylesheet">
	{% } %}
	{% if(o.chunkManifest) { %}
	<script>{%#o.chunkManifest%};</script>
	{% } %}
	<title>frontend-build-tools</title>
  </head>
  <body>
	  <div id="app">
	  </div>
	{% for (var chunk in o.htmlWebpackPlugin.files.chunks) { %}
	<script type="text/javascript" src="{%=o.htmlWebpackPlugin.files.chunks[chunk].entry%}"></script>
	{% } %}
  </body>
</html>
```

* 注: 首页模板支持pepper.configs.js配置的api, static, mode, globals等全局变量。具体用法:

```
<link rel="shortcut icon" href="{%=o.configs.STATIC%}assets/bundle.css">
<script>
    if ("{%=o.configs.MODE%}" == 'release') {
        he('send', 'error', {err: s.substr(0, 999)});
    }
</script>

```


### <span id="api">API</span>

| cli 命令                      | 说明
|-------------------------------|----------------------------
| pepper init [name]            | es6 & react-router
| pepper init-redux [name]      | es6 & react-router & redux
| pepper init-rn [name]         | react-native (演出票 DEMO)
| pepper page [name]            | 创建页面
| pepper component [name]       | 创建组件
| pepper upgrade                | 版本升级 (gitlab pull)
| pepper start [-p PORT]        | 本地调试，基于 start 配置
| pepper test                   | 运行测试代码, 基于 test 配置打包, 可后缀 `-s ` 启动服务
| pepper pre                    | 基于 pre 配置打包，压缩, 可后缀 `-s ` 启动服务
| pepper release                | 基于 release 配置打包，压缩，移除 console 日志, 可后缀 `-s ` 启动服务
| pepper library [mode]         | 基于 mode [test, pre, release] 模式将项目按照 umd 格式打包成类库，类库名称 `package.json` name 属性制定, [使用参考](#library) 

<br/> 也可以这样玩耍 (项目目录下输入 pepper)

```
➜  pepper
? 选择要执行的任务:
  项目打包
  代码格式校验
  开发调试
❯ 初始化新项目 (es5)
  初始化新项目 (es6 & react & react-router)
  初始化新项目 (es6 & react & react-router & redux)
  初始化新项目 (react-native & redux)
(Move up and down to reveal more choices)

```

### <span id="questions">常见问题</span>

* <b id="quest-clone">clone 后的 pepper 要放到哪里？</b>

  一定不要放到项目目录下，一定不要放到项目目录下，一定不要放到项目目录下，（建议放到应用程序目录，pepper 也是个程序嘛）

* <b id="quest-entry">程序入口在哪里？</b>

 在配置项中的 `pages` 有说明，它就是整个程序的入口，默认使用 `pages/index` 文件。这里定义了程序的路由配置，`path='/' component={INITIAL_PAGE}`，`INITIAL_PAGE` 就是应用的首页

* <b id="quest-proxy">`import PAGE from 'react-proxy?name=PAGE_NAME!./page_path` 是什么意思 ?</b>

  这个是使用 [react-proxy-loader](https://github.com/webpack/react-proxy-loader) 对页面进行拆分，由于每一页面对应一个[Router](https://github.com/reactjs/react-router)，从而实现了页面的按需加载。当然，如果不需要页面的懒加载功能，可以直接这样引用 `import PAGE from './page_path'`

* <b id="quest-image">怎么在组件或者页面中引用图片？</b>

  首先，图片、字体等静态资源要统一放到 `assets` 目录中去。
  其次，确保 `pepper.cofig.js` 中的 `alias` 中有 `assets` 的配置  
  
  之后，对于 js 中的图片地址，要先 import IMAGE from `assets/paths/to/your/image`, 然后 `<img src={IMAGE} />` 就可以了  
  
  SCSS 中的图片引用，要这样处理 `background-image: url(~assets/paths/to/your/image)`  
  
  [具体原理参考这里](http://gitlab.intra.wepiao.com/FEI/talks/tree/master/kick_ass_relative_path)

* <b id="quest-fonts">字体文件引用报错</b>

  对于本地字体文件的引用，请去除文件后缀名之后的所有参数(hash 或者 queryStr 之类的)

* <b id="quest-multi-template">多个 html 文件</b>

  不同的页面入口文件，可指定不同的 html 模版

* <b id="quest-manifest">chunkManifest的异步加载模块crossOrigin属性问题</b>

  release打包后，项目需要异步加载模块时，如果pepper.configis.js的globals存在crossOrigin变量，则把crossorigin属性加到script中

  效果如：`<script type="text/javascript" crossorigin="anonymous" charset="utf-8" async src="//mqqstatic.wepiao.com/tld/js/cinemas-e9243a4.js"></script>`

  具体配置:

  ```
  globals:{
      "crossOrigin": {
              "start"         :   "anonymous",
              "test"          :   "use-credentials",
              "pre"           :   "use-credentials",
              "release"       :   "anonymous"
      }
  }
  ```


  ```
  .
  ├── [ 232]  README.md
  ├── [ 170]  dist
  ├── [ 547]  mock.js
  ├── [1.8K]  node_modules
  ├── [ 391]  package.json
  ├── [2.8K]  pepper.config.js
  ├── [ 238]  src
  ├── [1.1K]  template.html
  └── [ 687]  test.html
  ```
  `pepper.config.js` 配置多页面入口

  ```
  // custom default page dir
  "pages": {
      // 生成的 html 文件的名称
      index: {
          // 入口文件路径，相对于 src 目录下
          entry: 'pages/index',
          // 模版文件路径 相对于 根目录
          template: 'template.html'
      },
      test: {
          entry: 'pages/test',
          template: 'test.html'
      }
  },
  ```

  最后打包生成不同的 html 文件 ( pepper release )

  ```
  Hash: c55a6f52505a36434410
  Version: webpack 1.13.2
  Time: 7106ms
                    Asset       Size  Chunks             Chunk Names
     js/index-47dbd820.js    57.6 kB    0, 4  [emitted]  index
      js/home-819f5bf8.js    10.2 kB    1, 4  [emitted]  home
      js/test-d4588b46.js  500 bytes    2, 4  [emitted]  test
    js/vendor-d10e59ab.js     131 kB    3, 4  [emitted]  vendor
  js/manifest-e2ed8267.js    1.29 kB       4  [emitted]  manifest
               index.html    1.95 kB          [emitted]
                test.html    1.59 kB          [emitted]
     [0] multi vendor 40 bytes {3} [built]
      + 239 hidden modules
  ```

  [Demo 地址](http://gitlab.intra.wepiao.com/FEI/pepper-mutiple-html-template-demo.git)

* <b id="quest-upgrade">pepper 怎么升级</b>

  墙裂建议采用 [gitlab 本地安装](#install)，这种方式升级较容易（终端中执行: `pepper upgrade` ，任意目录皆可)

* <span id="quest-template-mock">Start 模式下 替换 html 模版文件中的字符串</span>

  场景：html 文件中包含特定格式的字符串占位符，上线时该字符串由线上发布机替换成真实的值
  问题：线下开发，无法处理字符串占位符替换问题，阻碍开发进程
  解决方案：指定一个特定的文件，包含要替换的逻辑以及对应的值，每个 html 模版对应一个该文件，运行时做替换处理

  pepper 配置如下:

  单模版 html 配置：

  ```
  "template": {
      "path": "src/template.html", // custom template path, omit it if your desire to use inner template
	  "mock": "src/template.mock.js", // the replacement rules
      "favicon": "assets/images/favicon.ico"
  },
  ```

  多模版 html 配置：

  ```
  // custom default page dir
  "pages": {
      "index": {
          entry: "pages/index",
          template: "src/template.html", // target html to be replaced
          mock: "src/template.mock.js", // the replacement rules
      },
      "share": {
          entry: "pages/share",
          template: "src/template.html",
          mock: "src/template.mock.js",
      }
  },
  ```

  替换文件的规则：

  ```
  // 多个数组
  modules.exports = [
	// 子数组，前者替换规则的正则表达式, 后者为要替换的值
    [ /\[\[\[sSharePic\]\]\]/g,             "http://wx.wepiao.com/favicon.ico" ],
    [ /\[\[\[sShareTitle\]\]\]/g,           "微信电影票" ],
    [ /\[\[\[sShareContent\]\]\]/g,         "娱票儿，微信电影、演出票" ],
    [ /\[\[\[appShareChannels\]\]\]/g,      "1,3,4" ],
  ]
  // 没有使用键指对的原因在于，key 不能为正则表达式

  ```
  **注意：该规则只在开发（start）模式下有效，测试，预上线和线上由发布机做处理**

### <span id="library">关于用 pepper 打包公共组件库的说明</span>

现在 pepper 支持将项目按照 umd 的格式，打包成公共类库，跨项目使用。这个是为了解决一些公共组件跨项目使用的问题。

1. 按照 npm 的规范，将项目配置成也发布的格式。
2. 在 pepper.config.js 中制定库的入口文件, 默认 `src/index.js`

```
├── demo
├── package.json
├── pepper.config.js
├── src
│   ├── Banner
│   ├── FullScreen
│   ├── Picture
│   ├── index.js
│   ├── style.scss
│   └── utils
└── yarn.lock

```
具体参见 [Advertisement 项目配置](http://gitlab.intra.wepiao.com/FEI/Advertisement.git)


### <span id="npm">关于 npm 的常见问题</span>

* <b id="npm-install">npm install</b>

  这个命令会拉取 `package.json` 的 `dependencies`, `devDependencies` 列表中罗列的依赖包，并存放到 `node_modules` 中去。一般会在初次 `clone` 项目时用到。还有就是，如果某次代码更新后，`packages.json` 中有变动，或者终端中提示未找到某个模块之类的错误，也要执行下 `npm install`
  `npm install PACKAGE --save` 指在项目中安装 `PACKAGE` 依赖包，并保存到 `package.json` 的 `dependencies` 中去。
  `npm install PACKAGE --save-dev` 指在项目中安装 `PACKAGE` 依赖包，并保存到 `package.json` 的 `devDependencies` 中去。
  `--save` 和 `--save-dev` 的区别在于，前者是代码逻辑中的依赖（React），后者只是起辅助（代码校验，编译等等, 线上代码并不包含）
  [具体参考这里](https://docs.npmjs.com/cli/install)

* <b id="npm-shrinkwrap">npm shrinkwrap</b>

  这个会锁定依赖包的版本，而不是在每次 `npm install` 时使用最新的 `patch version`
  举个例子，项目中依赖 `vision-ui@^1.2.11` ，如果有新版 `vision-ui@1.2.12` ，那在首次 `npm install` 的时候就会下载 `vision-ui@1.2.12` 而非 `vision-ui@1.2.11`。这个与 npm 升级策略有关。强烈建议开启此项。
  [具体参考这里](https://docs.npmjs.com/cli/shrinkwrap)
