正在显示
100 个修改的文件
包含
2379 行增加
和
0 行删除
.autod.conf.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +module.exports = { | ||
4 | + write: true, | ||
5 | + prefix: '^', | ||
6 | + plugin: 'autod-egg', | ||
7 | + test: [ | ||
8 | + 'test', | ||
9 | + 'benchmark', | ||
10 | + ], | ||
11 | + devdep: [ | ||
12 | + 'egg', | ||
13 | + 'egg-ci', | ||
14 | + 'egg-bin', | ||
15 | + 'autod', | ||
16 | + 'autod-egg', | ||
17 | + 'eslint', | ||
18 | + 'eslint-config-egg', | ||
19 | + 'webstorm-disable-index', | ||
20 | + ], | ||
21 | + exclude: [ | ||
22 | + './test/fixtures', | ||
23 | + './docs', | ||
24 | + './coverage', | ||
25 | + ], | ||
26 | +}; |
.eslintignore
0 → 100644
.gitignore
0 → 100644
History.md
0 → 100644
1 | + | ||
2 | +1.8.0 / 2021-10-16 | ||
3 | +================== | ||
4 | + | ||
5 | +**features** | ||
6 | + * [[`cdab574`](http://github.com/eggjs/egg-view-assets/commit/cdab574d0d5a95533a6b7a8af34f850b0168b532)] - feat: support config.nonce (#42) (Yiyu He <<dead_horse@qq.com>>) | ||
7 | + | ||
8 | +1.7.0 / 2021-04-08 | ||
9 | +================== | ||
10 | + | ||
11 | +**features** | ||
12 | + * [[`6c5b719`](http://github.com/eggjs/egg-view-assets/commit/6c5b71913994a0f18bf4585df6245cbba0390e3f)] - feat: start detecting port from devServer.port (#40) (Chen Yangjian <<252317+cyjake@users.noreply.github.com>>) | ||
13 | + | ||
14 | +1.6.2 / 2021-03-22 | ||
15 | +================== | ||
16 | + | ||
17 | +**fixes** | ||
18 | + * [[`ee0012e`](http://github.com/eggjs/egg-view-assets/commit/ee0012eca841b1ce2b3264e8154a9a4e3d5f8c15)] - fix: 修复autoPort时,port数据格式和fs模块要求数据格式不匹配问题 (#39) (拾邑 <<mr_suanmei@163.com>>) | ||
19 | + | ||
20 | +1.6.1 / 2020-06-09 | ||
21 | +================== | ||
22 | + | ||
23 | +**features** | ||
24 | + * [[`c7ec6e6`](http://github.com/eggjs/egg-view-assets/commit/c7ec6e671220a71db0ba59ef485f064ca622ccd7)] - feat: make sure `__webpack_public_path__` inserted just once (#31) (viko16 <<viko16@users.noreply.github.com>>) | ||
25 | + | ||
26 | +**fixes** | ||
27 | + * [[`c992877`](http://github.com/eggjs/egg-view-assets/commit/c992877307e4c4178e3e337a5ca43a982e3254e4)] - fix: prefer http://127.0.0.1 even if https is on (#37) (Chen Yangjian <<252317+cyjake@users.noreply.github.com>>) | ||
28 | + | ||
29 | +1.6.0 / 2019-09-19 | ||
30 | +================== | ||
31 | + | ||
32 | +**features** | ||
33 | + * [[`287aedb`](http://github.com/eggjs/egg-view-assets/commit/287aedb4e1ee58861e76198a364e4ff9d7122d92)] - feat: set protocol according to app.options.https (Chen Yangjian <<252317+cyjake@users.noreply.github.com>>) | ||
34 | + | ||
35 | +1.5.0 / 2019-04-09 | ||
36 | +================== | ||
37 | + | ||
38 | +**features** | ||
39 | + * [[`226de2e`](http://github.com/eggjs/egg-view-assets/commit/226de2e5ed9d89bac203ead5a47f2a91fd9158ad)] - feat: script support crossorigin attribute (#30) (仙森 <<dapixp@gmail.com>>) | ||
40 | + | ||
41 | +1.4.4 / 2019-01-29 | ||
42 | +================== | ||
43 | + | ||
44 | +**fixes** | ||
45 | + * [[`8f0092e`](http://github.com/eggjs/egg-view-assets/commit/8f0092ef4e4dd33b63dbf836bdad72bc7a0b88be)] - fix: use w3c valid link format (#29) (fengmk2 <<fengmk2@gmail.com>>) | ||
46 | + | ||
47 | +1.4.3 / 2019-01-08 | ||
48 | +================== | ||
49 | + | ||
50 | +**fixes** | ||
51 | + * [[`f5966ec`](http://github.com/eggjs/egg-view-assets/commit/f5966eca57fcfe5819617e264555ba00c9c6c95b)] - fix(devServer): reset port when app get port from agent (#28) (Haoliang Gao <<sakura9515@gmail.com>>) | ||
52 | + * [[`a5866bc`](http://github.com/eggjs/egg-view-assets/commit/a5866bc0b47a0df4d024f44b9fc05d39494fcf47)] - fix: remove unnecessary console (#27) (Haoliang Gao <<sakura9515@gmail.com>>) | ||
53 | + | ||
54 | +1.4.2 / 2019-01-07 | ||
55 | +================== | ||
56 | + | ||
57 | +**features** | ||
58 | + * [[`f6e9fb7`](http://github.com/eggjs/egg-view-assets/commit/f6e9fb7b51435516ce9d7f20a8de2e7a62b87c61)] - feat(devServer): support autoport in env (#26) (Haoliang Gao <<sakura9515@gmail.com>>) | ||
59 | + | ||
60 | +1.4.1 / 2019-01-07 | ||
61 | +================== | ||
62 | + | ||
63 | +**fixes** | ||
64 | + * [[`9378984`](http://github.com/eggjs/egg-view-assets/commit/9378984ad71465eb9cc46beba4936e8bb95f97f6)] - fix: port should be paased from agent to app (#25) (Haoliang Gao <<sakura9515@gmail.com>>) | ||
65 | + | ||
66 | +1.4.0 / 2019-01-06 | ||
67 | +================== | ||
68 | + | ||
69 | +**features** | ||
70 | + * [[`59d20a0`](http://github.com/eggjs/egg-view-assets/commit/59d20a0e7f1451aee14cb1a5117e8cda2d999498)] - feat: auto check port with autoPort (#24) (Haoliang Gao <<sakura9515@gmail.com>>) | ||
71 | + | ||
72 | +**others** | ||
73 | + * [[`bf05a99`](http://github.com/eggjs/egg-view-assets/commit/bf05a99da9661aa731c6938ec7e8443dd381d2eb)] - refactor: use inherit instead of pipe (#22) (Haoliang Gao <<sakura9515@gmail.com>>) | ||
74 | + * [[`d233b74`](http://github.com/eggjs/egg-view-assets/commit/d233b74cc38648a310146802385031d001d971e9)] - test: set proc null when exit by self (#23) (Haoliang Gao <<sakura9515@gmail.com>>) | ||
75 | + | ||
76 | +1.3.0 / 2018-08-20 | ||
77 | +================== | ||
78 | + | ||
79 | +**others** | ||
80 | + * [[`0815337`](http://github.com/eggjs/egg-view-assets/commit/0815337586c10ed393786bb7c3ad8f08242a9135)] - refactor: no need to encode context with base64 (#21) (fengmk2 <<fengmk2@gmail.com>>) | ||
81 | + | ||
82 | +1.2.0 / 2018-08-15 | ||
83 | +================== | ||
84 | + | ||
85 | +**features** | ||
86 | + * [[`6d9be84`](http://github.com/eggjs/egg-view-assets/commit/6d9be84495ae1e789e20276f33f184e736cf7e86)] - feat: manifest add absolute path and complete path support (#20) (仙森 <<dapixp@gmail.com>>) | ||
87 | + | ||
88 | +1.1.2 / 2018-04-16 | ||
89 | +================== | ||
90 | + | ||
91 | +**others** | ||
92 | + * [[`9b32a6e`](http://github.com/eggjs/egg-view-assets/commit/9b32a6ed4e2219e323946fb987e900a1477d66d9)] - refactor: atob should be minified (#16) (Haoliang Gao <<sakura9515@gmail.com>>) | ||
93 | + | ||
94 | +1.1.1 / 2018-04-15 | ||
95 | +================== | ||
96 | + | ||
97 | +**fixes** | ||
98 | + * [[`6d8793b`](http://github.com/eggjs/egg-view-assets/commit/6d8793b43e98618b2be76101011ad8732906f114)] - fix: atob browser compatible (#15) (Haoliang Gao <<sakura9515@gmail.com>>) | ||
99 | + * [[`2475b7c`](http://github.com/eggjs/egg-view-assets/commit/2475b7c54fe088e2469181eb63f1a48c7c94fdbf)] - fix: command can be run on Windows (#13) (Haoliang Gao <<sakura9515@gmail.com>>) | ||
100 | + * [[`ab29dee`](http://github.com/eggjs/egg-view-assets/commit/ab29deed14e0b6d266fc5a90b8f46950ce096431)] - fix: set context data more safely (#14) (Yiyu He <<dead_horse@qq.com>>) | ||
101 | + | ||
102 | +1.1.0 / 2018-04-04 | ||
103 | +================== | ||
104 | + | ||
105 | +**features** | ||
106 | + * [[`772164a`](http://github.com/eggjs/egg-view-assets/commit/772164a3669610659cd43614caf7825a74f30c65)] - feat: unittest environment is same as local (#12) (Haoliang Gao <<sakura9515@gmail.com>>) | ||
107 | + | ||
108 | +1.0.4 / 2018-04-01 | ||
109 | +================== | ||
110 | + | ||
111 | +**fixes** | ||
112 | + * [[`9d7fdac`](http://github.com/eggjs/egg-view-assets/commit/9d7fdac51c6799db63d948b37e94ad47a280a7c4)] - fix: should transform jsx to js (#11) (Haoliang Gao <<sakura9515@gmail.com>>) | ||
113 | + | ||
114 | +1.0.3 / 2018-04-01 | ||
115 | +================== | ||
116 | + | ||
117 | +**fixes** | ||
118 | + * [[`f58d766`](http://github.com/eggjs/egg-view-assets/commit/f58d766907f6e18627a44d42b3513e41b71bcf09)] - fix: check port when devServer is enabled (#10) (Haoliang Gao <<sakura9515@gmail.com>>) | ||
119 | + | ||
120 | +1.0.2 / 2018-03-29 | ||
121 | +================== | ||
122 | + | ||
123 | +**fixes** | ||
124 | + * [[`3f02556`](http://github.com/eggjs/egg-view-assets/commit/3f0255680aa8f6460faf08957efdff184c781b3f)] - fix: publicPath should contains leading and trailing slash (#9) (Haoliang Gao <<sakura9515@gmail.com>>) | ||
125 | + * [[`5db9e90`](http://github.com/eggjs/egg-view-assets/commit/5db9e906e85a414ae276e7de1033c1539aff8ea5)] - fix: more safe context when locals is from query (#8) (Haoliang Gao <<sakura9515@gmail.com>>) | ||
126 | + | ||
127 | +1.0.1 / 2018-03-15 | ||
128 | +================== | ||
129 | + | ||
130 | +**fixes** | ||
131 | + * [[`2ebef9d`](http://github.com/eggjs/egg-view-assets/commit/2ebef9d86235cc8c13dc262f1b767bdad77cea02)] - fix: publicPath should be __webpack_public_path__ (#7) (Haoliang Gao <<sakura9515@gmail.com>>) | ||
132 | + | ||
133 | +1.0.0 / 2018-03-15 | ||
134 | +================== | ||
135 | + | ||
136 | +**others** | ||
137 | + * [[`e369e2c`](http://github.com/eggjs/egg-view-assets/commit/e369e2c924bb09e949f27e5095c29d7bcddda68a)] - docs: update readme (#6) (Haoliang Gao <<sakura9515@gmail.com>>),fatal: No names found, cannot describe anything. | ||
138 | + |
LICENSE
0 → 100644
1 | +MIT License | ||
2 | + | ||
3 | +Copyright (c) 2017-present Alibaba Group Holding Limited and other contributors. | ||
4 | + | ||
5 | +Permission is hereby granted, free of charge, to any person obtaining a copy | ||
6 | +of this software and associated documentation files (the "Software"), to deal | ||
7 | +in the Software without restriction, including without limitation the rights | ||
8 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
9 | +copies of the Software, and to permit persons to whom the Software is | ||
10 | +furnished to do so, subject to the following conditions: | ||
11 | + | ||
12 | +The above copyright notice and this permission notice shall be included in all | ||
13 | +copies or substantial portions of the Software. | ||
14 | + | ||
15 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
16 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
17 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
18 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
19 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
20 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
21 | +SOFTWARE. |
README.md
0 → 100644
1 | +# egg-view-assets | ||
2 | + | ||
3 | +[![NPM version][npm-image]][npm-url] | ||
4 | +[![build status][travis-image]][travis-url] | ||
5 | +[![Test coverage][codecov-image]][codecov-url] | ||
6 | +[![David deps][david-image]][david-url] | ||
7 | +[![Known Vulnerabilities][snyk-image]][snyk-url] | ||
8 | +[![npm download][download-image]][download-url] | ||
9 | + | ||
10 | +[npm-image]: https://img.shields.io/npm/v/egg-view-assets.svg?style=flat-square | ||
11 | +[npm-url]: https://npmjs.org/package/egg-view-assets | ||
12 | +[travis-image]: https://img.shields.io/travis/eggjs/egg-view-assets.svg?style=flat-square | ||
13 | +[travis-url]: https://travis-ci.org/eggjs/egg-view-assets | ||
14 | +[codecov-image]: https://img.shields.io/codecov/c/github/eggjs/egg-view-assets.svg?style=flat-square | ||
15 | +[codecov-url]: https://codecov.io/github/eggjs/egg-view-assets?branch=master | ||
16 | +[david-image]: https://img.shields.io/david/eggjs/egg-view-assets.svg?style=flat-square | ||
17 | +[david-url]: https://david-dm.org/eggjs/egg-view-assets | ||
18 | +[snyk-image]: https://snyk.io/test/npm/egg-view-assets/badge.svg?style=flat-square | ||
19 | +[snyk-url]: https://snyk.io/test/npm/egg-view-assets | ||
20 | +[download-image]: https://img.shields.io/npm/dm/egg-view-assets.svg?style=flat-square | ||
21 | +[download-url]: https://npmjs.org/package/egg-view-assets | ||
22 | + | ||
23 | +Manage frontend assets in development and production. | ||
24 | + | ||
25 | +## Install | ||
26 | + | ||
27 | +```bash | ||
28 | +$ npm i egg-view-assets --save | ||
29 | +``` | ||
30 | + | ||
31 | +## Usage | ||
32 | + | ||
33 | +Add `egg-view-assets` as plugin | ||
34 | + | ||
35 | +```js | ||
36 | +// {app_root}/config/plugin.js | ||
37 | +exports.assets = { | ||
38 | + enable: true, | ||
39 | + package: 'egg-view-assets', | ||
40 | +}; | ||
41 | +``` | ||
42 | + | ||
43 | +Configuration, you can see full example in [egg-ant-design-pro]. | ||
44 | + | ||
45 | +```js | ||
46 | +// {app_root}/config/config.default.js | ||
47 | +exports.view = { | ||
48 | + mapping: { | ||
49 | + '.js': 'assets', | ||
50 | + }, | ||
51 | +}; | ||
52 | + | ||
53 | +exports.assets = { | ||
54 | + devServer: { | ||
55 | + command: 'roadhog dev', | ||
56 | + port: 8000, | ||
57 | + }, | ||
58 | +}; | ||
59 | +``` | ||
60 | + | ||
61 | +See [config/config.default.js](config/config.default.js) for more detail. | ||
62 | + | ||
63 | +## Questions & Suggestions | ||
64 | + | ||
65 | +Please open an issue [here](https://github.com/eggjs/egg/issues). | ||
66 | + | ||
67 | +## License | ||
68 | + | ||
69 | +[MIT](LICENSE) | ||
70 | + | ||
71 | +[egg-ant-design-pro]: https://github.com/eggjs/egg-ant-design-pro |
agent.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const assert = require('assert'); | ||
4 | +const DevServer = require('./lib/dev_server'); | ||
5 | + | ||
6 | +module.exports = agent => startDevServer(agent); | ||
7 | + | ||
8 | +function startDevServer(agent) { | ||
9 | + const assetsConfig = agent.config.assets; | ||
10 | + | ||
11 | + if (!assetsConfig.isLocalOrUnittest) return; | ||
12 | + if (!assetsConfig.devServer.enable) return; | ||
13 | + | ||
14 | + assert(assetsConfig.devServer.autoPort || assetsConfig.devServer.port, 'port or autoPort is required when devServer is enabled'); | ||
15 | + | ||
16 | + const server = new DevServer(agent); | ||
17 | + server.ready(err => { | ||
18 | + if (err) agent.coreLogger.error('[egg-view-assets]', err.message); | ||
19 | + }); | ||
20 | + | ||
21 | + if (assetsConfig.devServer.waitStart) { | ||
22 | + agent.beforeStart(async () => { | ||
23 | + await server.ready(); | ||
24 | + }); | ||
25 | + } | ||
26 | + | ||
27 | + agent.beforeClose(async () => { | ||
28 | + await server.close(); | ||
29 | + }); | ||
30 | +} |
app.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const fs = require('fs'); | ||
4 | +const path = require('path'); | ||
5 | +const assert = require('assert'); | ||
6 | +const AssetsView = require('./lib/assets_view'); | ||
7 | + | ||
8 | +module.exports = app => { | ||
9 | + const assetsConfig = app.config.assets; | ||
10 | + | ||
11 | + if (assetsConfig.devServer.enable && assetsConfig.isLocalOrUnittest) { | ||
12 | + let port = assetsConfig.devServer.port; | ||
13 | + if (assetsConfig.devServer.autoPort === true) { | ||
14 | + try { | ||
15 | + port = fs.readFileSync(assetsConfig.devServer.portPath, 'utf8'); | ||
16 | + assetsConfig.devServer.port = Number(port); | ||
17 | + } catch (err) { | ||
18 | + // istanbul ignore next | ||
19 | + throw new Error('check autoPort fail'); | ||
20 | + } | ||
21 | + } | ||
22 | + const protocol = app.options.https && assetsConfig.dynamicLocalIP ? 'https' : 'http'; | ||
23 | + assetsConfig.url = `${protocol}://127.0.0.1:${port}`; | ||
24 | + } | ||
25 | + | ||
26 | + // it should check manifest.json on deployment | ||
27 | + if (!assetsConfig.isLocalOrUnittest) { | ||
28 | + const manifestPath = path.join(app.config.baseDir, 'config/manifest.json'); | ||
29 | + assert(fs.existsSync(manifestPath), `${manifestPath} is required`); | ||
30 | + assetsConfig.manifest = require(manifestPath); | ||
31 | + } | ||
32 | + | ||
33 | + app.view.use('assets', AssetsView); | ||
34 | +}; |
app/extend/helper.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const AssetsContext = require('../../lib/assets_context'); | ||
4 | + | ||
5 | +const HELPER_ASSETS = require('../../lib/util/constant').HELPER_ASSETS; | ||
6 | +const ASSETS = Symbol('Helper#assets'); | ||
7 | + | ||
8 | +module.exports = { | ||
9 | + get assets() { | ||
10 | + if (this[ASSETS]) return this[ASSETS]; | ||
11 | + if (this.ctx[HELPER_ASSETS]) { | ||
12 | + this[ASSETS] = this.ctx[HELPER_ASSETS]; | ||
13 | + } else { | ||
14 | + this[ASSETS] = new AssetsContext(this.ctx); | ||
15 | + } | ||
16 | + return this[ASSETS]; | ||
17 | + }, | ||
18 | +}; |
appveyor.yml
0 → 100644
1 | +environment: | ||
2 | + matrix: | ||
3 | + - nodejs_version: '8' | ||
4 | + - nodejs_version: '10' | ||
5 | + | ||
6 | +install: | ||
7 | + - ps: Install-Product node $env:nodejs_version | ||
8 | + - npm i npminstall && node_modules\.bin\npminstall | ||
9 | + | ||
10 | +test_script: | ||
11 | + - node --version | ||
12 | + - npm --version | ||
13 | + - npm run test | ||
14 | + | ||
15 | +build: off |
config/config.default.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const path = require('path'); | ||
4 | + | ||
5 | +module.exports = appInfo => ({ | ||
6 | + /** | ||
7 | + * assets options | ||
8 | + * @member Config#assets | ||
9 | + * @property {String} url - the host of the assets, it will be `http://127.0.0.1:${devServer.port}` in development. | ||
10 | + * @property {String} publicPath - the base path of the assets | ||
11 | + * @property {String} templatePath - the file path of template rendering html | ||
12 | + * @property {String} templateViewEngine - the view engine for rendering template | ||
13 | + * @property {Boolean} crossorigin - if script is cross origin set this config to true | ||
14 | + * @property {Boolean} contextKey - the property name of context, default is `context` | ||
15 | + * @property {Boolean} devServer - configuration of local assets server | ||
16 | + * @property {Boolean} devServer.command - a command for starting a server, such as `webpack` | ||
17 | + * @property {Boolean} devServer.port - listening port for the server, it will be checked when starting | ||
18 | + * @property {Boolean} devServer.timeout - the timeout for checking listening port | ||
19 | + * @property {Boolean} devServer.env - custom environment | ||
20 | + * @property {Boolean} devServer.debug - show stdout/stderr for devServer | ||
21 | + * @property {Boolean} devServer.waitStart - whether wait devServer starting | ||
22 | + * @property {Function} nonce(ctx) - dynamic generate nonce for csp(https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) | ||
23 | + */ | ||
24 | + assets: { | ||
25 | + isLocalOrUnittest: appInfo.env === 'local' || appInfo.env === 'unittest', | ||
26 | + url: '', | ||
27 | + publicPath: '', | ||
28 | + templatePath: '', | ||
29 | + templateViewEngine: '', | ||
30 | + crossorigin: false, | ||
31 | + contextKey: 'context', | ||
32 | + dynamicLocalIP: true, | ||
33 | + devServer: { | ||
34 | + enable: true, | ||
35 | + command: '', | ||
36 | + autoPort: false, | ||
37 | + port: null, | ||
38 | + portPath: path.join(appInfo.baseDir, 'run/assetsPort'), | ||
39 | + env: {}, | ||
40 | + debug: false, | ||
41 | + timeout: 60 * 1000, | ||
42 | + waitStart: false, | ||
43 | + }, | ||
44 | + }, | ||
45 | +}); |
config/config.unittest.js
0 → 100644
lib/assets_context.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const assert = require('assert'); | ||
4 | + | ||
5 | +// URL consists of host, resourceBase and entry, | ||
6 | +// E.X. http://127.0.0.1:7001/public/index.js | ||
7 | +// host is http://127.0.0.1:7001 | ||
8 | +// resourceBase is /public/ | ||
9 | +// entry is index.js | ||
10 | +class Assets { | ||
11 | + | ||
12 | + constructor(ctx) { | ||
13 | + this.ctx = ctx; | ||
14 | + this.config = ctx.app.config.assets; | ||
15 | + this.isLocalOrUnittest = this.config.isLocalOrUnittest; | ||
16 | + this.manifest = this.config.manifest; | ||
17 | + this.crossorigin = this.config.crossorigin; | ||
18 | + // publicPath should contain trailing / and leading / | ||
19 | + this.publicPath = this.isLocalOrUnittest ? '/' : normalizePublicPath(this.config.publicPath); | ||
20 | + // make sure __webpack_public_path__ inserted just once | ||
21 | + this.hasWebpackGlobalVariableInserted = false; | ||
22 | + this.host = this.config.url; | ||
23 | + this.resourceBase = `${this.host}${this.publicPath}`; | ||
24 | + this.nonce = this.config.nonce && this.config.nonce(this.ctx); | ||
25 | + } | ||
26 | + | ||
27 | + getStyle(entry) { | ||
28 | + entry = entry || this.entryCss; | ||
29 | + return linkTpl({ url: this.getURL(entry) }); | ||
30 | + } | ||
31 | + | ||
32 | + getScript(entry) { | ||
33 | + entry = entry || this.entry; | ||
34 | + | ||
35 | + let script = ''; | ||
36 | + if (this.publicPath && !this.hasWebpackGlobalVariableInserted) { | ||
37 | + script = inlineScriptTpl(`window.__webpack_public_path__ = '${this.publicPath}';`, this.nonce); | ||
38 | + this.hasWebpackGlobalVariableInserted = true; | ||
39 | + } | ||
40 | + | ||
41 | + script += scriptTpl({ | ||
42 | + url: this.getURL(entry), | ||
43 | + crossorigin: this.crossorigin, | ||
44 | + }); | ||
45 | + | ||
46 | + return script; | ||
47 | + } | ||
48 | + | ||
49 | + getContext(data) { | ||
50 | + data = safeStringify(data || this.assetsContext || {}); | ||
51 | + return inlineScriptTpl(`(function(){window.${this.config.contextKey} = JSON.parse(decodeURIComponent("${data}"));})()`, this.nonce); | ||
52 | + } | ||
53 | + | ||
54 | + getURL(entry) { | ||
55 | + let urlpath = entry; | ||
56 | + if (!this.isLocalOrUnittest) { | ||
57 | + urlpath = this.manifest[urlpath]; | ||
58 | + assert(urlpath, `Don't find ${entry} in manifest.json`); | ||
59 | + } | ||
60 | + | ||
61 | + if (urlpath.startsWith('/')) { | ||
62 | + return `${this.host}${urlpath}`; | ||
63 | + } | ||
64 | + | ||
65 | + if (urlpath.startsWith('http://') || urlpath.startsWith('https://')) { | ||
66 | + return urlpath; | ||
67 | + } | ||
68 | + | ||
69 | + return `${this.resourceBase}${urlpath}`; | ||
70 | + } | ||
71 | + | ||
72 | + setEntry(entry) { | ||
73 | + this.entry = entry.replace(/\.jsx?$/, '.js'); | ||
74 | + this.entryCss = entry.replace(/\.jsx?$/, '.css'); | ||
75 | + } | ||
76 | + | ||
77 | + setContext(context) { | ||
78 | + this.assetsContext = context; | ||
79 | + } | ||
80 | + | ||
81 | +} | ||
82 | + | ||
83 | +module.exports = Assets; | ||
84 | + | ||
85 | +function linkTpl({ url }) { | ||
86 | + return `<link rel="stylesheet" href="${url}" />`; | ||
87 | +} | ||
88 | + | ||
89 | +function scriptTpl({ url, crossorigin }) { | ||
90 | + return '<script' + (crossorigin ? ' crossorigin' : '') + ` src="${url}"></script>`; | ||
91 | +} | ||
92 | + | ||
93 | +function inlineScriptTpl(content, nonce) { | ||
94 | + const startTag = nonce ? `<script nonce=${nonce}>` : '<script>'; | ||
95 | + return `${startTag}${content}</script>`; | ||
96 | +} | ||
97 | + | ||
98 | +function safeStringify(data) { | ||
99 | + if (!data) return ''; | ||
100 | + return encodeURIComponent(JSON.stringify(data)); | ||
101 | +} | ||
102 | + | ||
103 | +function normalizePublicPath(publicPath) { | ||
104 | + if (!publicPath) return '/'; | ||
105 | + let firstIndex = 0; | ||
106 | + if (publicPath[firstIndex] === '/') firstIndex++; | ||
107 | + let lastIndex = publicPath.length - 1; | ||
108 | + if (publicPath[lastIndex] === '/') lastIndex--; | ||
109 | + return '/' + publicPath.slice(firstIndex, lastIndex + 1) + '/'; | ||
110 | +} |
lib/assets_view.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const fs = require('mz/fs'); | ||
4 | +const TEMPLATE_CACHE = Symbol('AssetsView#templateCache'); | ||
5 | +const HELPER_ASSETS = require('./util/constant').HELPER_ASSETS; | ||
6 | +const renderDefault = require('./util/default_template'); | ||
7 | + | ||
8 | + | ||
9 | +class AssetsView { | ||
10 | + constructor(ctx) { | ||
11 | + this.ctx = ctx; | ||
12 | + this.config = ctx.app.config.assets; | ||
13 | + this.logger = ctx.coreLogger; | ||
14 | + | ||
15 | + if (!ctx.app[TEMPLATE_CACHE]) ctx.app[TEMPLATE_CACHE] = new Map(); | ||
16 | + this.cache = ctx.app[TEMPLATE_CACHE]; | ||
17 | + } | ||
18 | + | ||
19 | + async render(name, locals, options) { | ||
20 | + const templateViewEngine = options.templateViewEngine || this.config.templateViewEngine; | ||
21 | + const templatePath = options.templatePath || this.config.templatePath; | ||
22 | + | ||
23 | + const assets = this.ctx.helper.assets; | ||
24 | + assets.setEntry(options.name); | ||
25 | + assets.setContext(options.locals); | ||
26 | + | ||
27 | + if (templateViewEngine && templatePath) { | ||
28 | + // helper.assets will use this instance | ||
29 | + this.ctx[HELPER_ASSETS] = assets; | ||
30 | + | ||
31 | + const templateStr = await this.readFileWithCache(templatePath); | ||
32 | + this.logger.info('[egg-view-assets] use %s to render %s, entry is %s', | ||
33 | + templateViewEngine, templatePath, name); | ||
34 | + return await this.ctx.renderString(templateStr, locals, { | ||
35 | + viewEngine: templateViewEngine, | ||
36 | + }); | ||
37 | + } | ||
38 | + | ||
39 | + return renderDefault(assets); | ||
40 | + } | ||
41 | + | ||
42 | + async readFileWithCache(filepath) { | ||
43 | + let str = this.cache.get(filepath); | ||
44 | + if (str) return str; | ||
45 | + | ||
46 | + str = await fs.readFile(filepath, 'utf8'); | ||
47 | + this.cache.set(filepath, str); | ||
48 | + return str; | ||
49 | + } | ||
50 | + | ||
51 | + async renderString() { | ||
52 | + throw new Error('assets engine don\'t support renderString'); | ||
53 | + } | ||
54 | + | ||
55 | +} | ||
56 | + | ||
57 | +module.exports = AssetsView; |
lib/dev_server.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const path = require('path'); | ||
4 | +const fs = require('mz/fs'); | ||
5 | +const spawn = require('cross-spawn'); | ||
6 | +const Base = require('sdk-base'); | ||
7 | +const sleep = require('mz-modules/sleep'); | ||
8 | +const awaitEvent = require('await-event'); | ||
9 | +const debug = require('debug')('egg-view-assets:dev_server'); | ||
10 | +const detectPort = require('detect-port'); | ||
11 | +const mkdirp = require('mz-modules/mkdirp'); | ||
12 | +const is = require('is-type-of'); | ||
13 | + | ||
14 | + | ||
15 | +class DevServer extends Base { | ||
16 | + constructor(app) { | ||
17 | + super({ | ||
18 | + initMethod: 'init', | ||
19 | + }); | ||
20 | + this.app = app; | ||
21 | + this.isClosed = false; | ||
22 | + } | ||
23 | + | ||
24 | + async init() { | ||
25 | + const { devServer } = this.app.config.assets; | ||
26 | + | ||
27 | + if (devServer.autoPort) { | ||
28 | + devServer.port = await detectPort(devServer.port || 10000); | ||
29 | + await mkdirp(path.dirname(devServer.portPath)); | ||
30 | + await fs.writeFile(devServer.portPath, devServer.port.toString()); | ||
31 | + } else { | ||
32 | + // check whether the port is using | ||
33 | + if (await this.checkPortExist()) { | ||
34 | + throw new Error(`port ${this.app.config.assets.devServer.port} has been used`); | ||
35 | + } | ||
36 | + } | ||
37 | + | ||
38 | + // start dev server asynchronously | ||
39 | + this.startAsync(); | ||
40 | + await this.waitListen(); | ||
41 | + } | ||
42 | + | ||
43 | + startAsync() { | ||
44 | + const { devServer } = this.app.config.assets; | ||
45 | + devServer.command = this.replacePort(devServer.command); | ||
46 | + const [ command, ...args ] = devServer.command.split(/\s+/); | ||
47 | + | ||
48 | + const env = Object.assign({}, process.env, devServer.env); | ||
49 | + // env.PATH = `${this.app.config.baseDir}/node_modules/.bin:${env.PATH}`; | ||
50 | + // replace {port} | ||
51 | + Object.keys(env).forEach(key => { | ||
52 | + env[key] = this.replacePort(env[key]); | ||
53 | + }); | ||
54 | + const opt = { | ||
55 | + // disable stdout by default | ||
56 | + stdio: [ 'inherit', 'ignore', 'inherit' ], | ||
57 | + env, | ||
58 | + shell: process.platform === 'win32' | ||
59 | + }; | ||
60 | + if (devServer.cwd) opt.cwd = devServer.cwd; | ||
61 | + if (devServer.debug) opt.stdio[1] = 'inherit'; | ||
62 | + const proc = this.proc = spawn(command, args, opt); | ||
63 | + proc.once('error', err => this.exit(err)); | ||
64 | + proc.once('exit', code => this.exit(code)); | ||
65 | + } | ||
66 | + | ||
67 | + async checkPortExist() { | ||
68 | + const { devServer } = this.app.config.assets; | ||
69 | + const port = await detectPort(devServer.port); | ||
70 | + debug('check %s, get result %s', devServer.port, port); | ||
71 | + return port !== devServer.port; | ||
72 | + } | ||
73 | + | ||
74 | + async waitListen() { | ||
75 | + const logger = this.app.coreLogger; | ||
76 | + const { devServer } = this.app.config.assets; | ||
77 | + let timeout = devServer.timeout / 1000; | ||
78 | + let isSuccess = false; | ||
79 | + while (timeout > 0) { | ||
80 | + /* istanbul ignore if */ | ||
81 | + if (this.isClosed) { | ||
82 | + logger.warn('[egg-view-assets] Closing, but devServer is not listened'); | ||
83 | + return; | ||
84 | + } | ||
85 | + if (await this.checkPortExist()) { | ||
86 | + logger.warn('[egg-view-assets] Run "%s" success, listen on %s', devServer.command, devServer.port); | ||
87 | + // 成功启动 | ||
88 | + isSuccess = true; | ||
89 | + break; | ||
90 | + } | ||
91 | + timeout--; | ||
92 | + await sleep(1000); | ||
93 | + debug('waiting, %s remain', timeout); | ||
94 | + } | ||
95 | + | ||
96 | + if (isSuccess) return; | ||
97 | + const err = new Error(`Run "${devServer.command}" failed after ${devServer.timeout / 1000}s`); | ||
98 | + throw err; | ||
99 | + } | ||
100 | + | ||
101 | + async close() { | ||
102 | + this.isClosed = true; | ||
103 | + /* istanbul ignore if */ | ||
104 | + if (!this.proc) return; | ||
105 | + this.app.coreLogger.warn('[egg-view-assets] dev server will be killed'); | ||
106 | + this.proc.kill(); | ||
107 | + await awaitEvent(this.proc, 'exit'); | ||
108 | + this.proc = null; | ||
109 | + } | ||
110 | + | ||
111 | + exit(codeOrError) { | ||
112 | + const logger = this.app.coreLogger; | ||
113 | + this.proc = null; | ||
114 | + | ||
115 | + if (!(codeOrError instanceof Error)) { | ||
116 | + const { devServer } = this.app.config.assets; | ||
117 | + const code = codeOrError; | ||
118 | + const message = `[egg-view-assets] Run "${devServer.command}" exit with code ${code}`; | ||
119 | + if (!code || code === 0) { | ||
120 | + logger.info(message); | ||
121 | + return; | ||
122 | + } | ||
123 | + | ||
124 | + codeOrError = new Error(message); | ||
125 | + } | ||
126 | + | ||
127 | + logger.error(codeOrError); | ||
128 | + } | ||
129 | + | ||
130 | + replacePort(str) { | ||
131 | + if (!is.string(str)) return str; | ||
132 | + return str.replace('{port}', this.app.config.assets.devServer.port); | ||
133 | + } | ||
134 | + | ||
135 | +} | ||
136 | + | ||
137 | +module.exports = DevServer; |
lib/util/constant.js
0 → 100644
lib/util/default_template.js
0 → 100644
package.json
0 → 100644
1 | +{ | ||
2 | + "name": "egg-view-assets", | ||
3 | + "eggPlugin": { | ||
4 | + "name": "assets", | ||
5 | + "dependencies": [ | ||
6 | + "view" | ||
7 | + ] | ||
8 | + }, | ||
9 | + "version": "1.8.0", | ||
10 | + "description": "Manage frontend assets in development and production", | ||
11 | + "keywords": [ | ||
12 | + "egg", | ||
13 | + "eggPlugin", | ||
14 | + "egg-plugin", | ||
15 | + "assets" | ||
16 | + ], | ||
17 | + "dependencies": { | ||
18 | + "await-event": "^2.1.0", | ||
19 | + "cross-spawn": "^6.0.5", | ||
20 | + "debug": "^4.1.1", | ||
21 | + "detect-port": "^1.3.0", | ||
22 | + "is-type-of": "^1.2.1", | ||
23 | + "mz": "^2.7.0", | ||
24 | + "mz-modules": "^2.1.0", | ||
25 | + "sdk-base": "^3.5.1" | ||
26 | + }, | ||
27 | + "devDependencies": { | ||
28 | + "autod": "^3.0.1", | ||
29 | + "autod-egg": "^1.1.0", | ||
30 | + "egg": "^2.14.2", | ||
31 | + "egg-bin": "^4.10.0", | ||
32 | + "egg-ci": "^1.11.0", | ||
33 | + "egg-mock": "^3.21.0", | ||
34 | + "egg-view-ejs": "^2.0.0", | ||
35 | + "egg-view-nunjucks": "^2.1.6", | ||
36 | + "eslint": "^5.12.0", | ||
37 | + "eslint-config-egg": "^7.1.0", | ||
38 | + "puppeteer": "^1.11.0", | ||
39 | + "supertest": "^3.3.0", | ||
40 | + "uglify-js": "^3.3.21", | ||
41 | + "urllib": "^2.34.1", | ||
42 | + "webstorm-disable-index": "^1.2.0" | ||
43 | + }, | ||
44 | + "engines": { | ||
45 | + "node": ">=8.0.0" | ||
46 | + }, | ||
47 | + "scripts": { | ||
48 | + "dev": "egg-bin dev", | ||
49 | + "test": "npm run lint -- --fix && egg-bin pkgfiles && npm run test-local", | ||
50 | + "test-local": "egg-bin test", | ||
51 | + "cov": "egg-bin cov", | ||
52 | + "lint": "eslint .", | ||
53 | + "ci": "egg-bin pkgfiles --check && npm run lint && npm run cov", | ||
54 | + "pkgfiles": "egg-bin pkgfiles", | ||
55 | + "autod": "autod", | ||
56 | + "uglify": "uglifyjs lib/fixtures/decode.js -o lib/fixtures/decode.min.js" | ||
57 | + }, | ||
58 | + "files": [ | ||
59 | + "app", | ||
60 | + "lib", | ||
61 | + "config", | ||
62 | + "agent.js", | ||
63 | + "app.js" | ||
64 | + ], | ||
65 | + "ci": { | ||
66 | + "type": "github", | ||
67 | + "version": "8, 10, 12, 14, 16", | ||
68 | + "license": true | ||
69 | + }, | ||
70 | + "repository": { | ||
71 | + "type": "git", | ||
72 | + "url": "git+https://github.com/eggjs/egg-view-assets.git" | ||
73 | + }, | ||
74 | + "bugs": { | ||
75 | + "url": "https://github.com/eggjs/egg/issues" | ||
76 | + }, | ||
77 | + "homepage": "https://github.com/eggjs/egg-view-assets#readme", | ||
78 | + "author": "popomore", | ||
79 | + "license": "MIT" | ||
80 | +} |
test/assets.test.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const path = require('path'); | ||
4 | +const mock = require('egg-mock'); | ||
5 | +const fs = require('mz/fs'); | ||
6 | +const assert = require('assert'); | ||
7 | +const urllib = require('urllib'); | ||
8 | +const address = require('address'); | ||
9 | + | ||
10 | +describe('test/assets.test.js', () => { | ||
11 | + | ||
12 | + afterEach(mock.restore); | ||
13 | + | ||
14 | + describe('AssetsView with default template', () => { | ||
15 | + let app; | ||
16 | + | ||
17 | + describe('local', () => { | ||
18 | + before(() => { | ||
19 | + mock.env('local'); | ||
20 | + app = mock.cluster({ | ||
21 | + baseDir: 'apps/assets', | ||
22 | + }); | ||
23 | + // app.debug(); | ||
24 | + return app.ready(); | ||
25 | + }); | ||
26 | + after(() => app.close()); | ||
27 | + | ||
28 | + it('should GET /', () => { | ||
29 | + return app.httpRequest() | ||
30 | + .get('/') | ||
31 | + .expect(/<div id="root"><\/div>/) | ||
32 | + .expect(/<link rel="stylesheet" href="http:\/\/127.0.0.1:8000\/index.css" \/>/) | ||
33 | + .expect(/<script src="http:\/\/127.0.0.1:8000\/index.js"><\/script>/) | ||
34 | + .expect(/<script>window.__webpack_public_path__ = '\/';<\/script>/) | ||
35 | + .expect(res => { | ||
36 | + assert(res.text.includes('<script>(function(){window.context = JSON.parse(decodeURIComponent("%7B%22data%22%3A1%7D"));})()<\/script>')); | ||
37 | + }) | ||
38 | + .expect(200); | ||
39 | + }); | ||
40 | + | ||
41 | + it('should GET jsx', () => { | ||
42 | + return app.httpRequest() | ||
43 | + .get('/account') | ||
44 | + .expect(/<link rel="stylesheet" href="http:\/\/127.0.0.1:8000\/account.css" \/>/) | ||
45 | + .expect(/<script src="http:\/\/127.0.0.1:8000\/account.js"><\/script>/) | ||
46 | + .expect(200); | ||
47 | + }); | ||
48 | + }); | ||
49 | + | ||
50 | + describe('production', () => { | ||
51 | + let app; | ||
52 | + | ||
53 | + before(() => { | ||
54 | + mock.env('prod'); | ||
55 | + app = mock.cluster({ | ||
56 | + baseDir: 'apps/assets', | ||
57 | + }); | ||
58 | + return app.ready(); | ||
59 | + }); | ||
60 | + after(() => app.close()); | ||
61 | + | ||
62 | + it('should GET /', () => { | ||
63 | + return app.httpRequest() | ||
64 | + .get('/') | ||
65 | + .expect(/<div id="root"><\/div>/) | ||
66 | + .expect(/<link rel="stylesheet" href="http:\/\/cdn.com\/app\/public\/index.b8e2efea.css" \/>/) | ||
67 | + .expect(/<script src="http:\/\/cdn.com\/app\/public\/index.c4ae6394.js"><\/script>/) | ||
68 | + .expect(/<script>window.__webpack_public_path__ = '\/app\/public\/';<\/script>/) | ||
69 | + .expect(res => { | ||
70 | + assert(res.text.includes('<script>(function(){window.context = JSON.parse(decodeURIComponent("%7B%22data%22%3A1%7D"));})()<\/script>')); | ||
71 | + }) | ||
72 | + .expect(200); | ||
73 | + }); | ||
74 | + }); | ||
75 | + }); | ||
76 | + | ||
77 | + describe('AssetsView with custom template', () => { | ||
78 | + let app; | ||
79 | + | ||
80 | + describe('local', () => { | ||
81 | + before(() => { | ||
82 | + mock.env('local'); | ||
83 | + app = mock.cluster({ | ||
84 | + baseDir: 'apps/assets-template', | ||
85 | + }); | ||
86 | + return app.ready(); | ||
87 | + }); | ||
88 | + after(() => app.close()); | ||
89 | + | ||
90 | + it('should GET /', () => { | ||
91 | + return app.httpRequest() | ||
92 | + .get('/') | ||
93 | + .expect(/<div id="root"><\/div>/) | ||
94 | + .expect(/<link rel="stylesheet" href="http:\/\/127.0.0.1:8000\/index.css" \/>/) | ||
95 | + .expect(/<script src="http:\/\/127.0.0.1:8000\/index.js"><\/script>/) | ||
96 | + .expect(res => { | ||
97 | + assert(res.text.includes('<script>(function(){window.context = JSON.parse(decodeURIComponent("%7B%7D"));})()<\/script>')); | ||
98 | + }) | ||
99 | + .expect(200); | ||
100 | + }); | ||
101 | + | ||
102 | + it('should render context', () => { | ||
103 | + return app.httpRequest() | ||
104 | + .get('/context') | ||
105 | + .expect(res => { | ||
106 | + assert(res.text.includes('<script>(function(){window.context = JSON.parse(decodeURIComponent("%7B%22data%22%3A1%7D"));})()<\/script>')); | ||
107 | + }) | ||
108 | + .expect(200); | ||
109 | + }); | ||
110 | + | ||
111 | + it('should use template from render options', () => { | ||
112 | + return app.httpRequest() | ||
113 | + .get('/options') | ||
114 | + .expect(/<div id="root"><\/div>/) | ||
115 | + .expect(/<link rel="stylesheet" href="http:\/\/127.0.0.1:8000\/index.css" \/>/) | ||
116 | + .expect(/<script src="http:\/\/127.0.0.1:8000\/index.js"><\/script>/) | ||
117 | + .expect(res => { | ||
118 | + assert(res.text.includes('<script>(function(){window.context = JSON.parse(decodeURIComponent("%7B%7D"));})()<\/script>')); | ||
119 | + }) | ||
120 | + .expect(200); | ||
121 | + }); | ||
122 | + | ||
123 | + it('should use cache when template exist', async () => { | ||
124 | + const template = path.join(__dirname, 'fixtures/apps/assets-template/app/view/cache.html'); | ||
125 | + await fs.writeFile(template, '{{ data }}'); | ||
126 | + | ||
127 | + await app.httpRequest() | ||
128 | + .get('/cache') | ||
129 | + .expect(res => { | ||
130 | + assert(res.text.includes('<script>(function(){window.context = JSON.parse(decodeURIComponent("%7B%22data%22%3A1%7D"));})()<\/script>')); | ||
131 | + }) | ||
132 | + .expect(200); | ||
133 | + | ||
134 | + await fs.writeFile(template, 'override'); | ||
135 | + | ||
136 | + await app.httpRequest() | ||
137 | + .get('/cache') | ||
138 | + .expect(res => { | ||
139 | + assert(res.text.includes('<script>(function(){window.context = JSON.parse(decodeURIComponent("%7B%22data%22%3A1%7D"));})()<\/script>')); | ||
140 | + }) | ||
141 | + .expect(200); | ||
142 | + }); | ||
143 | + | ||
144 | + it('should throw when call renderString', () => { | ||
145 | + return app.httpRequest() | ||
146 | + .get('/renderString') | ||
147 | + .expect(/assets engine don't support renderString/) | ||
148 | + .expect(500); | ||
149 | + }); | ||
150 | + }); | ||
151 | + | ||
152 | + describe('prod', () => { | ||
153 | + before(() => { | ||
154 | + mock.env('prod'); | ||
155 | + app = mock.cluster({ | ||
156 | + baseDir: 'apps/assets-template', | ||
157 | + }); | ||
158 | + return app.ready(); | ||
159 | + }); | ||
160 | + after(() => app.close()); | ||
161 | + | ||
162 | + it('should GET /', () => { | ||
163 | + return app.httpRequest() | ||
164 | + .get('/') | ||
165 | + .expect(/<div id="root"><\/div>/) | ||
166 | + .expect(/<link rel="stylesheet" href="http:\/\/cdn.com\/index.b8e2efea.css" \/>/) | ||
167 | + .expect(/<script src="http:\/\/cdn.com\/index.c4ae6394.js"><\/script>/) | ||
168 | + .expect(res => { | ||
169 | + assert(res.text.includes('<script>(function(){window.context = JSON.parse(decodeURIComponent("%7B%7D"));})()<\/script>')); | ||
170 | + }) | ||
171 | + .expect(200); | ||
172 | + }); | ||
173 | + }); | ||
174 | + }); | ||
175 | + | ||
176 | + describe('in other view engine', () => { | ||
177 | + let app; | ||
178 | + | ||
179 | + describe('local', () => { | ||
180 | + before(() => { | ||
181 | + mock.env('local'); | ||
182 | + app = mock.cluster({ | ||
183 | + baseDir: 'apps/other-view-engine', | ||
184 | + }); | ||
185 | + return app.ready(); | ||
186 | + }); | ||
187 | + after(() => app.close()); | ||
188 | + | ||
189 | + it('should GET /', () => { | ||
190 | + return app.httpRequest() | ||
191 | + .get('/') | ||
192 | + .expect(/<link rel="stylesheet" href="http:\/\/127.0.0.1:8000\/index.css" \/>/) | ||
193 | + .expect(/<script src="http:\/\/127.0.0.1:8000\/index.js"><\/script>/) | ||
194 | + .expect(/<script>window.__webpack_public_path__ = '\/';<\/script>/) | ||
195 | + .expect(/<script>window.resourceBaseUrl = 'http:\/\/127.0.0.1:8000\/';<\/script/) | ||
196 | + .expect(res => { | ||
197 | + assert(res.text.includes('<script>(function(){window.context = JSON.parse(decodeURIComponent("%7B%22data%22%3A1%7D"));})()<\/script>')); | ||
198 | + }) | ||
199 | + .expect(200); | ||
200 | + }); | ||
201 | + }); | ||
202 | + | ||
203 | + describe('prod', () => { | ||
204 | + before(() => { | ||
205 | + mock.env('prod'); | ||
206 | + app = mock.cluster({ | ||
207 | + baseDir: 'apps/other-view-engine', | ||
208 | + }); | ||
209 | + return app.ready(); | ||
210 | + }); | ||
211 | + after(() => app.close()); | ||
212 | + | ||
213 | + it('should GET /', () => { | ||
214 | + return app.httpRequest() | ||
215 | + .get('/') | ||
216 | + .expect(/<link rel="stylesheet" href="http:\/\/cdn.com\/app\/public\/index.b8e2efea.css" \/>/) | ||
217 | + .expect(/<script src="http:\/\/cdn.com\/app\/public\/index.c4ae6394.js"><\/script>/) | ||
218 | + .expect(/<script>window.__webpack_public_path__ = '\/app\/public\/';<\/script>/) | ||
219 | + .expect(/<script>window.resourceBaseUrl = 'http:\/\/cdn.com\/app\/public\/';<\/script/) | ||
220 | + .expect(res => { | ||
221 | + assert(res.text.includes('<script>(function(){window.context = JSON.parse(decodeURIComponent("%7B%22data%22%3A1%7D"));})()<\/script>')); | ||
222 | + }) | ||
223 | + .expect(200); | ||
224 | + }); | ||
225 | + }); | ||
226 | + }); | ||
227 | + | ||
228 | + describe('custom assets.url', () => { | ||
229 | + let app; | ||
230 | + | ||
231 | + before(() => { | ||
232 | + mock.env('prod'); | ||
233 | + app = mock.cluster({ | ||
234 | + baseDir: 'apps/custom-assets-url', | ||
235 | + }); | ||
236 | + return app.ready(); | ||
237 | + }); | ||
238 | + after(() => app.close()); | ||
239 | + | ||
240 | + it('should GET /', () => { | ||
241 | + return app.httpRequest() | ||
242 | + .get('/') | ||
243 | + .expect(/<link rel="stylesheet" href="http:\/\/localhost\/index.b8e2efea.css" \/>/) | ||
244 | + .expect(200); | ||
245 | + }); | ||
246 | + }); | ||
247 | + | ||
248 | + describe('https assets.url with dynamicLocalIP', () => { | ||
249 | + let app; | ||
250 | + | ||
251 | + before(() => { | ||
252 | + mock.env('local'); | ||
253 | + app = mock.cluster({ | ||
254 | + baseDir: 'apps/https', | ||
255 | + port: 8443, | ||
256 | + https: { | ||
257 | + cert: path.join(__dirname, 'fixtures/apps/https/server.cert'), | ||
258 | + key: path.join(__dirname, 'fixtures/apps/https/server.key'), | ||
259 | + }, | ||
260 | + }); | ||
261 | + return app.ready(); | ||
262 | + }); | ||
263 | + after(() => app.close()); | ||
264 | + | ||
265 | + it('should GET /', () => { | ||
266 | + return urllib.request('https://127.0.0.1:8443', { | ||
267 | + dataType: 'text', | ||
268 | + rejectUnauthorized: false, | ||
269 | + }).then(response => { | ||
270 | + assert(response.status === 200); | ||
271 | + assert(response.data.includes('https://127.0.0.1:8000/index.css')); | ||
272 | + }); | ||
273 | + }); | ||
274 | + }); | ||
275 | + | ||
276 | + describe('https assets.url without dynamicLocalIP', () => { | ||
277 | + let app; | ||
278 | + | ||
279 | + before(() => { | ||
280 | + mock.env('local'); | ||
281 | + app = mock.cluster({ | ||
282 | + baseDir: 'apps/https-dynamic-ip', | ||
283 | + port: 8443, | ||
284 | + https: { | ||
285 | + cert: path.join(__dirname, 'fixtures/apps/https-dynamic-ip/server.cert'), | ||
286 | + key: path.join(__dirname, 'fixtures/apps/https-dynamic-ip/server.key'), | ||
287 | + }, | ||
288 | + }); | ||
289 | + return app.ready(); | ||
290 | + }); | ||
291 | + after(() => app.close()); | ||
292 | + | ||
293 | + it('should GET /', () => { | ||
294 | + return urllib.request(`https://${address.ip()}:8443`, { | ||
295 | + dataType: 'text', | ||
296 | + rejectUnauthorized: false, | ||
297 | + }).then(response => { | ||
298 | + assert(response.status === 200); | ||
299 | + assert(response.data.includes('http://127.0.0.1:8000/index.css')); | ||
300 | + }); | ||
301 | + }); | ||
302 | + }); | ||
303 | + | ||
304 | + describe('custom contextKey', () => { | ||
305 | + let app; | ||
306 | + | ||
307 | + before(() => { | ||
308 | + mock.env('local'); | ||
309 | + app = mock.cluster({ | ||
310 | + baseDir: 'apps/custom-context-key', | ||
311 | + }); | ||
312 | + return app.ready(); | ||
313 | + }); | ||
314 | + after(() => app.close()); | ||
315 | + | ||
316 | + it('should GET /', () => { | ||
317 | + return app.httpRequest() | ||
318 | + .get('/') | ||
319 | + .expect(/window.__context__ =/) | ||
320 | + .expect(200); | ||
321 | + }); | ||
322 | + }); | ||
323 | + | ||
324 | + describe('context security', () => { | ||
325 | + let app; | ||
326 | + | ||
327 | + before(() => { | ||
328 | + app = mock.cluster({ | ||
329 | + baseDir: 'apps/context-security', | ||
330 | + }); | ||
331 | + return app.ready(); | ||
332 | + }); | ||
333 | + after(() => app.close()); | ||
334 | + | ||
335 | + it('should GET /', () => { | ||
336 | + return app.httpRequest() | ||
337 | + .get('/?query=<x%E2%80%A8x>') | ||
338 | + .expect(res => { | ||
339 | + assert(res.text.includes('<script>(function(){window.context = JSON.parse(decodeURIComponent("%7B%22query%22%3A%22%3Cx%E2%80%A8x%3E%22%7D"));})()<\/script>')); | ||
340 | + }) | ||
341 | + .expect(200); | ||
342 | + }); | ||
343 | + }); | ||
344 | + | ||
345 | + describe('publicPath', () => { | ||
346 | + let app; | ||
347 | + | ||
348 | + describe('local', () => { | ||
349 | + before(() => { | ||
350 | + mock.env('local'); | ||
351 | + app = mock.app({ | ||
352 | + baseDir: 'apps/custom-public-path', | ||
353 | + }); | ||
354 | + return app.ready(); | ||
355 | + }); | ||
356 | + after(() => app.close()); | ||
357 | + | ||
358 | + it('should render with trailing /', () => { | ||
359 | + mock(app.config.assets, 'publicPath', '/public/'); | ||
360 | + | ||
361 | + let ctx = app.mockContext(); | ||
362 | + ctx.helper.assets.setEntry('index.js'); | ||
363 | + let script = ctx.helper.assets.getScript(); | ||
364 | + assert(script.includes('__webpack_public_path__ = \'/\';')); | ||
365 | + assert(script.includes('src="/index.js"')); | ||
366 | + let style = ctx.helper.assets.getStyle(); | ||
367 | + assert(style.includes('href="/index.css"')); | ||
368 | + | ||
369 | + ctx = app.mockContext(); | ||
370 | + script = ctx.helper.assets.getScript('index.js'); | ||
371 | + assert(script.includes('__webpack_public_path__ = \'/\';')); | ||
372 | + assert(script.includes('src="/index.js"')); | ||
373 | + style = ctx.helper.assets.getStyle('index.css'); | ||
374 | + assert(style.includes('href="/index.css"')); | ||
375 | + }); | ||
376 | + | ||
377 | + it('should render without trailing /', () => { | ||
378 | + mock(app.config.assets, 'publicPath', '/public'); | ||
379 | + | ||
380 | + let ctx = app.mockContext(); | ||
381 | + ctx.helper.assets.setEntry('index.js'); | ||
382 | + let script = ctx.helper.assets.getScript(); | ||
383 | + assert(script.includes('__webpack_public_path__ = \'/\';')); | ||
384 | + assert(script.includes('src="/index.js"')); | ||
385 | + let style = ctx.helper.assets.getStyle(); | ||
386 | + assert(style.includes('href="/index.css"')); | ||
387 | + | ||
388 | + ctx = app.mockContext(); | ||
389 | + script = ctx.helper.assets.getScript('index.js'); | ||
390 | + assert(script.includes('__webpack_public_path__ = \'/\';')); | ||
391 | + assert(script.includes('src="/index.js"')); | ||
392 | + style = ctx.helper.assets.getStyle('index.css'); | ||
393 | + assert(style.includes('href="/index.css"')); | ||
394 | + }); | ||
395 | + }); | ||
396 | + | ||
397 | + describe('prod', () => { | ||
398 | + before(() => { | ||
399 | + mock.env('prod'); | ||
400 | + app = mock.app({ | ||
401 | + baseDir: 'apps/custom-public-path', | ||
402 | + }); | ||
403 | + return app.ready(); | ||
404 | + }); | ||
405 | + after(() => app.close()); | ||
406 | + | ||
407 | + it('should render with trailing /', () => { | ||
408 | + mock(app.config.assets, 'publicPath', '/public/'); | ||
409 | + | ||
410 | + let ctx = app.mockContext(); | ||
411 | + ctx.helper.assets.setEntry('index.js'); | ||
412 | + let script = ctx.helper.assets.getScript(); | ||
413 | + assert(script.includes('__webpack_public_path__ = \'/public/\';')); | ||
414 | + assert(script.includes('src="/public/index.js"')); | ||
415 | + let style = ctx.helper.assets.getStyle(); | ||
416 | + assert(style.includes('href="/public/index.css"')); | ||
417 | + | ||
418 | + ctx = app.mockContext(); | ||
419 | + script = ctx.helper.assets.getScript('index.js'); | ||
420 | + assert(script.includes('__webpack_public_path__ = \'/public/\';')); | ||
421 | + assert(script.includes('src="/public/index.js"')); | ||
422 | + style = ctx.helper.assets.getStyle('index.css'); | ||
423 | + assert(style.includes('href="/public/index.css"')); | ||
424 | + }); | ||
425 | + | ||
426 | + it('should render without trailing /', () => { | ||
427 | + mock(app.config.assets, 'publicPath', '/public'); | ||
428 | + | ||
429 | + let ctx = app.mockContext(); | ||
430 | + ctx.helper.assets.setEntry('index.js'); | ||
431 | + let script = ctx.helper.assets.getScript(); | ||
432 | + assert(script.includes('__webpack_public_path__ = \'/public/\';')); | ||
433 | + assert(script.includes('src="/public/index.js"')); | ||
434 | + let style = ctx.helper.assets.getStyle(); | ||
435 | + assert(style.includes('href="/public/index.css"')); | ||
436 | + | ||
437 | + ctx = app.mockContext(); | ||
438 | + script = ctx.helper.assets.getScript('index.js'); | ||
439 | + assert(script.includes('__webpack_public_path__ = \'/public/\';')); | ||
440 | + assert(script.includes('src="/public/index.js"')); | ||
441 | + style = ctx.helper.assets.getStyle('index.css'); | ||
442 | + assert(style.includes('href="/public/index.css"')); | ||
443 | + }); | ||
444 | + }); | ||
445 | + }); | ||
446 | + | ||
447 | + describe('manifest checking', () => { | ||
448 | + let app; | ||
449 | + afterEach(() => app.close()); | ||
450 | + | ||
451 | + it('should check manifest.json on prod', async () => { | ||
452 | + mock.env('prod'); | ||
453 | + app = mock.app({ | ||
454 | + baseDir: 'apps/no-manifest', | ||
455 | + }); | ||
456 | + // app.debug(); | ||
457 | + try { | ||
458 | + await app.ready(); | ||
459 | + throw new Error('should not run'); | ||
460 | + } catch (err) { | ||
461 | + assert(err.message === path.join(__dirname, 'fixtures/apps/no-manifest/config/manifest.json') + ' is required'); | ||
462 | + } | ||
463 | + }); | ||
464 | + | ||
465 | + it('should not check manifest.json on local', async () => { | ||
466 | + mock.env('local'); | ||
467 | + app = mock.app({ | ||
468 | + baseDir: 'apps/no-manifest', | ||
469 | + }); | ||
470 | + // app.debug(); | ||
471 | + await app.ready(); | ||
472 | + }); | ||
473 | + | ||
474 | + it('should not check manifest.json on unittest', async () => { | ||
475 | + mock.env('unittest'); | ||
476 | + app = mock.app({ | ||
477 | + baseDir: 'apps/no-manifest', | ||
478 | + }); | ||
479 | + // app.debug(); | ||
480 | + await app.ready(); | ||
481 | + }); | ||
482 | + }); | ||
483 | + | ||
484 | + describe('complex manifest', () => { | ||
485 | + let app; | ||
486 | + before(() => { | ||
487 | + mock.env('default'); | ||
488 | + app = mock.app({ | ||
489 | + baseDir: 'apps/complex-manifest', | ||
490 | + }); | ||
491 | + return app.ready(); | ||
492 | + }); | ||
493 | + | ||
494 | + after(() => app.close()); | ||
495 | + afterEach(mock.restore); | ||
496 | + | ||
497 | + it('should publicPath work', () => { | ||
498 | + const ctx = app.mockContext(); | ||
499 | + ctx.helper.assets.setEntry('index.js'); | ||
500 | + const script = ctx.helper.assets.getScript(); | ||
501 | + assert(script.includes('__webpack_public_path__ = \'/public\/\';')); | ||
502 | + assert(script.includes('src="/public/index.js"')); | ||
503 | + }); | ||
504 | + | ||
505 | + it('should contain host if setting assets.url', () => { | ||
506 | + mock(app.config.assets, 'url', 'http://remotehost'); | ||
507 | + const ctx = app.mockContext(); | ||
508 | + ctx.helper.assets.setEntry('index.js'); | ||
509 | + const script = ctx.helper.assets.getScript(); | ||
510 | + assert(script.includes('__webpack_public_path__ = \'/public\/\';')); | ||
511 | + assert(script.includes('src="http://remotehost/public/index.js"')); | ||
512 | + const style = ctx.helper.assets.getStyle(); | ||
513 | + assert(style.includes('href="http://remotehost/index.css"')); | ||
514 | + }); | ||
515 | + | ||
516 | + it('should assets.publicPath not work if resource path is a absolute url', () => { | ||
517 | + const ctx = app.mockContext(); | ||
518 | + const style = ctx.helper.assets.getStyle('index.css'); | ||
519 | + assert(style.includes('href="/index.css"')); | ||
520 | + }); | ||
521 | + | ||
522 | + it('should assets.url not work if resource path is a complete url', () => { | ||
523 | + mock(app.config.assets, 'url', 'http://remotehost'); | ||
524 | + const ctx = app.mockContext(); | ||
525 | + const script = ctx.helper.assets.getScript('page1.js'); | ||
526 | + assert(script.includes('src="http://cdn.com/page1.js"')); | ||
527 | + }); | ||
528 | + }); | ||
529 | + | ||
530 | + describe('support crossorigin', () => { | ||
531 | + let app; | ||
532 | + before(() => { | ||
533 | + mock.env('default'); | ||
534 | + app = mock.app({ | ||
535 | + baseDir: 'apps/crossorigin', | ||
536 | + }); | ||
537 | + return app.ready(); | ||
538 | + }); | ||
539 | + | ||
540 | + after(() => app.close()); | ||
541 | + afterEach(mock.restore); | ||
542 | + | ||
543 | + it('should works', () => { | ||
544 | + const ctx = app.mockContext(); | ||
545 | + ctx.helper.assets.setEntry('index.js'); | ||
546 | + const script = ctx.helper.assets.getScript(); | ||
547 | + assert(script.includes('crossorigin')); | ||
548 | + }); | ||
549 | + }); | ||
550 | + | ||
551 | + describe('should insert webpack global variable just once', () => { | ||
552 | + let app; | ||
553 | + before(() => { | ||
554 | + mock.env('default'); | ||
555 | + app = mock.app({ | ||
556 | + baseDir: 'apps/multiple-getscript', | ||
557 | + }); | ||
558 | + return app.ready(); | ||
559 | + }); | ||
560 | + | ||
561 | + after(() => app.close()); | ||
562 | + afterEach(mock.restore); | ||
563 | + | ||
564 | + it('should works', () => { | ||
565 | + const ctx = app.mockContext(); | ||
566 | + | ||
567 | + const script = ctx.helper.assets.getScript('vendor.js'); | ||
568 | + assert(script.includes('__webpack_public_path__ = \'/\';')); | ||
569 | + assert(script.includes('src="/vendor.js"')); | ||
570 | + | ||
571 | + [ 'a.js', 'b.js', 'c.js' ].forEach(file => { | ||
572 | + const anotherScript = ctx.helper.assets.getScript(file); | ||
573 | + assert(!anotherScript.includes('__webpack_public_path__')); | ||
574 | + assert(anotherScript.includes(`src="/${file}"`)); | ||
575 | + }); | ||
576 | + | ||
577 | + }); | ||
578 | + }); | ||
579 | + | ||
580 | + describe('AssetsView with nonce', () => { | ||
581 | + let app; | ||
582 | + | ||
583 | + describe('local', () => { | ||
584 | + before(() => { | ||
585 | + mock.env('local'); | ||
586 | + app = mock.cluster({ | ||
587 | + baseDir: 'apps/assets-nonce', | ||
588 | + }); | ||
589 | + app.debug(); | ||
590 | + return app.ready(); | ||
591 | + }); | ||
592 | + after(() => app.close()); | ||
593 | + | ||
594 | + it('should GET /', () => { | ||
595 | + return app.httpRequest() | ||
596 | + .get('/') | ||
597 | + .expect(/<div id="root"><\/div>/) | ||
598 | + .expect(/<link rel="stylesheet" href="http:\/\/127.0.0.1:8000\/index.css" \/>/) | ||
599 | + .expect(/<script src="http:\/\/127.0.0.1:8000\/index.js"><\/script>/) | ||
600 | + .expect(/<script nonce=cspnonce>window.__webpack_public_path__ = '\/';<\/script>/) | ||
601 | + .expect(res => { | ||
602 | + assert(res.text.includes('<script nonce=cspnonce>(function(){window.context = JSON.parse(decodeURIComponent("%7B%22data%22%3A1%7D"));})()<\/script>')); | ||
603 | + }) | ||
604 | + .expect(200); | ||
605 | + }); | ||
606 | + | ||
607 | + it('should GET jsx', () => { | ||
608 | + return app.httpRequest() | ||
609 | + .get('/account') | ||
610 | + .expect(/<link rel="stylesheet" href="http:\/\/127.0.0.1:8000\/account.css" \/>/) | ||
611 | + .expect(/<script src="http:\/\/127.0.0.1:8000\/account.js"><\/script>/) | ||
612 | + .expect(200); | ||
613 | + }); | ||
614 | + }); | ||
615 | + | ||
616 | + describe('production', () => { | ||
617 | + let app; | ||
618 | + | ||
619 | + before(() => { | ||
620 | + mock.env('prod'); | ||
621 | + app = mock.cluster({ | ||
622 | + baseDir: 'apps/assets-nonce', | ||
623 | + }); | ||
624 | + return app.ready(); | ||
625 | + }); | ||
626 | + after(() => app.close()); | ||
627 | + | ||
628 | + it('should GET /', () => { | ||
629 | + return app.httpRequest() | ||
630 | + .get('/') | ||
631 | + .expect(/<div id="root"><\/div>/) | ||
632 | + .expect(/<link rel="stylesheet" href="http:\/\/cdn.com\/app\/public\/index.b8e2efea.css" \/>/) | ||
633 | + .expect(/<script src="http:\/\/cdn.com\/app\/public\/index.c4ae6394.js"><\/script>/) | ||
634 | + .expect(/<script nonce=cspnonce>window.__webpack_public_path__ = '\/app\/public\/';<\/script>/) | ||
635 | + .expect(res => { | ||
636 | + assert(res.text.includes('<script nonce=cspnonce>(function(){window.context = JSON.parse(decodeURIComponent("%7B%22data%22%3A1%7D"));})()<\/script>')); | ||
637 | + }) | ||
638 | + .expect(200); | ||
639 | + }); | ||
640 | + }); | ||
641 | + }); | ||
642 | +}); |
test/dev_server.test.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const net = require('net'); | ||
4 | +const path = require('path'); | ||
5 | +const mock = require('egg-mock'); | ||
6 | +const assert = require('assert'); | ||
7 | +const sleep = require('mz-modules/sleep'); | ||
8 | + | ||
9 | +describe('test/dev_server.test.js', () => { | ||
10 | + | ||
11 | + let app; | ||
12 | + afterEach(mock.restore); | ||
13 | + afterEach(() => app.close()); | ||
14 | + afterEach(() => sleep(5000)); | ||
15 | + | ||
16 | + it('should start/stop dev server', async () => { | ||
17 | + mock.env('local'); | ||
18 | + app = mock.cluster({ | ||
19 | + baseDir: 'apps/assets', | ||
20 | + }); | ||
21 | + app.debug(); | ||
22 | + await app.ready(); | ||
23 | + const reg = new RegExp(`Run "node ${path.join(__dirname, 'fixtures/apps/mocktool/server.js')}" success, listen on 8000`); | ||
24 | + app.expect('stdout', reg); | ||
25 | + | ||
26 | + await app.close(); | ||
27 | + app.expect('stdout', /\[egg-view-assets] dev server will be killed/); | ||
28 | + app.expect('stdout', /server stopped/); | ||
29 | + app.expect('stderr', /\[server] error/); | ||
30 | + }); | ||
31 | + | ||
32 | + it('should check first when port has been listened', async () => { | ||
33 | + mock.env('local'); | ||
34 | + const server = new net.Server(); | ||
35 | + server.listen(8000); | ||
36 | + app = mock.cluster({ | ||
37 | + baseDir: 'apps/assets', | ||
38 | + }); | ||
39 | + // app.debug(); | ||
40 | + await app.ready(); | ||
41 | + app.notExpect('stdout', /listen on 8000/); | ||
42 | + app.expect('stderr', /port 8000 has been used/); | ||
43 | + app.expect('stderr', /\[agent_worker] start error/); | ||
44 | + server.close(); | ||
45 | + }); | ||
46 | + | ||
47 | + it('should success when run command', async () => { | ||
48 | + mock.env('local'); | ||
49 | + app = mock.cluster({ | ||
50 | + baseDir: 'apps/not-listen', | ||
51 | + }); | ||
52 | + app.debug(); | ||
53 | + await app.ready(); | ||
54 | + | ||
55 | + await app.close(); | ||
56 | + await sleep(5000); | ||
57 | + | ||
58 | + // app.expect('stdout', /Closing, but devServer is not listened/); | ||
59 | + }); | ||
60 | + | ||
61 | + it('should custom devServer.cwd', async () => { | ||
62 | + mock(process.env, 'DEV_SERVER_DEBUG', true); | ||
63 | + mock.env('local'); | ||
64 | + app = mock.cluster({ | ||
65 | + baseDir: 'apps/custom-dev-server', | ||
66 | + }); | ||
67 | + app.debug(); | ||
68 | + await app.ready(); | ||
69 | + | ||
70 | + assert(app.stdout.includes('[server] cwd: ' + path.join(__dirname, 'fixtures/apps/custom-dev-server/config'))); | ||
71 | + }); | ||
72 | + | ||
73 | + it('should custom devServer.env', async () => { | ||
74 | + mock(process.env, 'DEV_SERVER_DEBUG', true); | ||
75 | + mock.env('local'); | ||
76 | + app = mock.cluster({ | ||
77 | + baseDir: 'apps/custom-dev-server', | ||
78 | + }); | ||
79 | + app.debug(); | ||
80 | + await app.ready(); | ||
81 | + | ||
82 | + assert(app.stdout.includes('[server] DEBUG: true')); | ||
83 | + }); | ||
84 | + | ||
85 | + it('should disable devServer.debug', async () => { | ||
86 | + mock(process.env, 'DEV_SERVER_DEBUG', false); | ||
87 | + mock.env('local'); | ||
88 | + app = mock.cluster({ | ||
89 | + baseDir: 'apps/custom-dev-server', | ||
90 | + }); | ||
91 | + app.debug(); | ||
92 | + await app.ready(); | ||
93 | + | ||
94 | + assert(!app.stdout.includes(path.join(__dirname, 'fixtures/apps/custom-dev-server/config'))); | ||
95 | + }); | ||
96 | + | ||
97 | + it('should log error when run command error', async () => { | ||
98 | + mock(process.env, 'DEV_SERVER_DEBUG', true); | ||
99 | + mock(process.env, 'EXIT', true); | ||
100 | + mock.env('local'); | ||
101 | + app = mock.cluster({ | ||
102 | + baseDir: 'apps/custom-dev-server', | ||
103 | + }); | ||
104 | + app.debug(); | ||
105 | + await app.ready(); | ||
106 | + | ||
107 | + const server = path.join(__dirname, 'fixtures/apps/custom-dev-server/config/server.js'); | ||
108 | + const errMsg = `[egg-view-assets] Run "node ${server}" exit with code 1`; | ||
109 | + assert(app.stderr.includes(errMsg)); | ||
110 | + }); | ||
111 | + | ||
112 | + it('should wait timeout', async () => { | ||
113 | + mock(process.env, 'DEV_SERVER_DEBUG', true); | ||
114 | + mock.env('local'); | ||
115 | + app = mock.cluster({ | ||
116 | + baseDir: 'apps/custom-dev-server', | ||
117 | + }); | ||
118 | + app.debug(); | ||
119 | + await app.ready(); | ||
120 | + | ||
121 | + await sleep(10000); | ||
122 | + const server = path.join(__dirname, 'fixtures/apps/custom-dev-server/config/server.js'); | ||
123 | + const errMsg = `[egg-view-assets] Run "node ${server}" failed after 5s`; | ||
124 | + assert(app.stderr.includes(errMsg)); | ||
125 | + }); | ||
126 | + | ||
127 | + it('should throw when command error', async () => { | ||
128 | + mock.env('local'); | ||
129 | + app = mock.cluster({ | ||
130 | + baseDir: 'apps/command-error', | ||
131 | + }); | ||
132 | + app.debug(); | ||
133 | + await app.ready(); | ||
134 | + | ||
135 | + app.expect('stderr', /spawn unknown ENOENT/); | ||
136 | + app.expect('stderr', /Run "unknown command" failed after 5s/); | ||
137 | + }); | ||
138 | + | ||
139 | + it('should check port when devServer is enabled', async () => { | ||
140 | + mock.env('local'); | ||
141 | + app = mock.cluster({ | ||
142 | + baseDir: 'apps/dev-server-no-port', | ||
143 | + }); | ||
144 | + // app.debug(); | ||
145 | + await app.ready(); | ||
146 | + | ||
147 | + app.expect('code', 1); | ||
148 | + app.expect('stderr', /port or autoPort is required when devServer is enabled/); | ||
149 | + }); | ||
150 | + | ||
151 | + it('should not check port when devServer is disabled', async () => { | ||
152 | + mock.env('local'); | ||
153 | + mock(process.env, 'DEV_SERVER_ENABLE', 'false'); | ||
154 | + app = mock.cluster({ | ||
155 | + baseDir: 'apps/dev-server-no-port', | ||
156 | + }); | ||
157 | + app.debug(); | ||
158 | + await app.ready(); | ||
159 | + | ||
160 | + app.expect('code', 0); | ||
161 | + app.expect('stdout', /egg started/); | ||
162 | + }); | ||
163 | + | ||
164 | + it('should auto check port with autoPort', async () => { | ||
165 | + mock.env('local'); | ||
166 | + const app1 = mock.cluster({ | ||
167 | + baseDir: 'apps/autoport', | ||
168 | + }); | ||
169 | + // app1.debug(); | ||
170 | + await app1.ready(); | ||
171 | + | ||
172 | + await app1.httpRequest() | ||
173 | + .get('/') | ||
174 | + .expect(/http:\/\/127.0.0.1:10000\/index.js/) | ||
175 | + .expect(200); | ||
176 | + await app1.httpRequest() | ||
177 | + .get('/port') | ||
178 | + .expect('10000') | ||
179 | + .expect(200); | ||
180 | + | ||
181 | + app1.expect('stdout', /\[server] listening 10000/); | ||
182 | + app1.expect('stdout', /\[server] SOCKET_SERVER: http:\/\/127.0.0.1:10000/); | ||
183 | + | ||
184 | + app = mock.cluster({ | ||
185 | + baseDir: 'apps/autoport', | ||
186 | + }); | ||
187 | + // app.debug(); | ||
188 | + try { | ||
189 | + await app.ready(); | ||
190 | + | ||
191 | + app.expect('stdout', /\[server] listening 10001/); | ||
192 | + } finally { | ||
193 | + await app1.close(); | ||
194 | + } | ||
195 | + }); | ||
196 | + | ||
197 | + it('should auto check port with autoPort and port offset', async () => { | ||
198 | + mock.env('local'); | ||
199 | + const app1 = mock.cluster({ | ||
200 | + baseDir: 'apps/autoport-offset', | ||
201 | + }); | ||
202 | + | ||
203 | + await app1.ready(); | ||
204 | + | ||
205 | + await app1.httpRequest() | ||
206 | + .get('/') | ||
207 | + .expect(/http:\/\/127.0.0.1:8000\/index.js/) | ||
208 | + .expect(200); | ||
209 | + await app1.httpRequest() | ||
210 | + .get('/port') | ||
211 | + .expect('8000') | ||
212 | + .expect(200); | ||
213 | + | ||
214 | + app1.expect('stdout', /\[server] listening 8000/); | ||
215 | + app1.expect('stdout', /\[server] SOCKET_SERVER: http:\/\/127.0.0.1:8000/); | ||
216 | + | ||
217 | + app = mock.cluster({ | ||
218 | + baseDir: 'apps/autoport-offset', | ||
219 | + }); | ||
220 | + // app.debug(); | ||
221 | + try { | ||
222 | + await app.ready(); | ||
223 | + | ||
224 | + app.expect('stdout', /\[server] listening 8001/); | ||
225 | + } finally { | ||
226 | + await app1.close(); | ||
227 | + } | ||
228 | + }); | ||
229 | +}); |
1 | +'use strict'; | ||
2 | + | ||
3 | +const Controller = require('egg').Controller; | ||
4 | + | ||
5 | +class HomeController extends Controller { | ||
6 | + async index() { | ||
7 | + await this.ctx.render('index.js', { | ||
8 | + data: 1, | ||
9 | + }); | ||
10 | + } | ||
11 | + | ||
12 | + async account() { | ||
13 | + await this.ctx.render('account.jsx'); | ||
14 | + } | ||
15 | + | ||
16 | + async renderString() { | ||
17 | + await this.ctx.renderString('', {}, { | ||
18 | + viewEngine: 'assets', | ||
19 | + }); | ||
20 | + } | ||
21 | +} | ||
22 | + | ||
23 | +module.exports = HomeController; |
1 | +'use strict'; | ||
2 | + | ||
3 | +const path = require('path'); | ||
4 | + | ||
5 | +exports.keys = '123456'; | ||
6 | +exports.view = { | ||
7 | + mapping: { | ||
8 | + '.js': 'assets', | ||
9 | + '.jsx': 'assets', | ||
10 | + }, | ||
11 | +}; | ||
12 | +exports.assets = { | ||
13 | + publicPath: '/app/public', | ||
14 | + devServer: { | ||
15 | + waitStart: true, | ||
16 | + command: 'node ' + path.join(__dirname, '../../mocktool/server.js'), | ||
17 | + port: 8000, | ||
18 | + env: {}, | ||
19 | + debug: true, | ||
20 | + }, | ||
21 | + nonce(ctx) { | ||
22 | + return ctx.cspnonce; | ||
23 | + }, | ||
24 | +}; |
1 | +{ | ||
2 | + "0.a94eff34.async.js": "0.a94eff34.async.js", | ||
3 | + "1.0647e11d.async.js": "1.0647e11d.async.js", | ||
4 | + "2.cbbca76b.async.js": "2.cbbca76b.async.js", | ||
5 | + "constants.js": "constants.e5eb201e.js", | ||
6 | + "index.css": "index.b8e2efea.css", | ||
7 | + "index.js": "index.c4ae6394.js", | ||
8 | + "static/yay.jpg": "static/yay.44dd3333.jpg" | ||
9 | +} |
test/fixtures/apps/assets-nonce/package.json
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const path = require('path'); | ||
4 | +const Controller = require('egg').Controller; | ||
5 | + | ||
6 | +class HomeController extends Controller { | ||
7 | + async index() { | ||
8 | + await this.ctx.render('index.js'); | ||
9 | + } | ||
10 | + | ||
11 | + async context() { | ||
12 | + await this.ctx.render('index.js', { | ||
13 | + data: 1, | ||
14 | + }); | ||
15 | + } | ||
16 | + | ||
17 | + async options() { | ||
18 | + await this.ctx.render('index.js', {}, { | ||
19 | + templatePath: path.join(__dirname, '../view/template.ejs'), | ||
20 | + templateViewEngine: 'ejs', | ||
21 | + }); | ||
22 | + } | ||
23 | + | ||
24 | + async cache() { | ||
25 | + await this.ctx.render('index.js', { data: 1 }, { | ||
26 | + templatePath: path.join(__dirname, '../view/template.ejs'), | ||
27 | + templateViewEngine: 'ejs', | ||
28 | + }); | ||
29 | + } | ||
30 | + | ||
31 | + async renderString() { | ||
32 | + await this.ctx.renderString('index.js', { data: 1 }, { | ||
33 | + viewEngine: 'assets', | ||
34 | + }); | ||
35 | + } | ||
36 | + | ||
37 | +} | ||
38 | + | ||
39 | +module.exports = HomeController; |
1 | +'use strict'; | ||
2 | + | ||
3 | +module.exports = app => { | ||
4 | + const { router, controller } = app; | ||
5 | + | ||
6 | + router.get('/', controller.home.index); | ||
7 | + router.get('/context', controller.home.context); | ||
8 | + router.get('/options', controller.home.options); | ||
9 | + router.get('/cache', controller.home.cache); | ||
10 | + router.get('/renderString', controller.home.renderString); | ||
11 | +}; |
1 | +'use strict'; | ||
2 | + | ||
3 | +const path = require('path'); | ||
4 | + | ||
5 | +exports.keys = '123456'; | ||
6 | +exports.view = { | ||
7 | + mapping: { | ||
8 | + '.js': 'assets', | ||
9 | + }, | ||
10 | +}; | ||
11 | +exports.assets = { | ||
12 | + templatePath: path.join(__dirname, '../app/view/template.html'), | ||
13 | + templateViewEngine: 'nunjucks', | ||
14 | + devServer: { | ||
15 | + waitStart: true, | ||
16 | + command: 'node ' + path.join(__dirname, '../../mocktool/server.js'), | ||
17 | + port: 8000, | ||
18 | + env: {}, | ||
19 | + debug: true, | ||
20 | + }, | ||
21 | +}; |
1 | +{ | ||
2 | + "0.a94eff34.async.js": "0.a94eff34.async.js", | ||
3 | + "1.0647e11d.async.js": "1.0647e11d.async.js", | ||
4 | + "2.cbbca76b.async.js": "2.cbbca76b.async.js", | ||
5 | + "constants.js": "constants.e5eb201e.js", | ||
6 | + "index.css": "index.b8e2efea.css", | ||
7 | + "index.js": "index.c4ae6394.js", | ||
8 | + "static/yay.jpg": "static/yay.44dd3333.jpg" | ||
9 | +} |
1 | +'use strict'; | ||
2 | + | ||
3 | +const Controller = require('egg').Controller; | ||
4 | + | ||
5 | +class HomeController extends Controller { | ||
6 | + async index() { | ||
7 | + await this.ctx.render('index.js', { | ||
8 | + data: 1, | ||
9 | + }); | ||
10 | + } | ||
11 | + | ||
12 | + async account() { | ||
13 | + await this.ctx.render('account.jsx'); | ||
14 | + } | ||
15 | + | ||
16 | + async renderString() { | ||
17 | + await this.ctx.renderString('', {}, { | ||
18 | + viewEngine: 'assets', | ||
19 | + }); | ||
20 | + } | ||
21 | +} | ||
22 | + | ||
23 | +module.exports = HomeController; |
test/fixtures/apps/assets/app/router.js
0 → 100644
test/fixtures/apps/assets/app/view/index.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const path = require('path'); | ||
4 | + | ||
5 | +exports.keys = '123456'; | ||
6 | +exports.view = { | ||
7 | + mapping: { | ||
8 | + '.js': 'assets', | ||
9 | + '.jsx': 'assets', | ||
10 | + }, | ||
11 | +}; | ||
12 | +exports.assets = { | ||
13 | + publicPath: '/app/public', | ||
14 | + devServer: { | ||
15 | + waitStart: true, | ||
16 | + command: 'node ' + path.join(__dirname, '../../mocktool/server.js'), | ||
17 | + port: 8000, | ||
18 | + env: {}, | ||
19 | + debug: true, | ||
20 | + }, | ||
21 | +}; |
1 | +{ | ||
2 | + "0.a94eff34.async.js": "0.a94eff34.async.js", | ||
3 | + "1.0647e11d.async.js": "1.0647e11d.async.js", | ||
4 | + "2.cbbca76b.async.js": "2.cbbca76b.async.js", | ||
5 | + "constants.js": "constants.e5eb201e.js", | ||
6 | + "index.css": "index.b8e2efea.css", | ||
7 | + "index.js": "index.c4ae6394.js", | ||
8 | + "static/yay.jpg": "static/yay.44dd3333.jpg" | ||
9 | +} |
test/fixtures/apps/assets/package.json
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const Controller = require('egg').Controller; | ||
4 | + | ||
5 | +class HomeController extends Controller { | ||
6 | + async index() { | ||
7 | + await this.ctx.render('index.js'); | ||
8 | + } | ||
9 | + | ||
10 | + async port() { | ||
11 | + this.ctx.body = this.app.config.assets.devServer.port; | ||
12 | + } | ||
13 | +} | ||
14 | + | ||
15 | +module.exports = HomeController; |
1 | +'use strict'; | ||
2 | + | ||
3 | +const path = require('path'); | ||
4 | + | ||
5 | +exports.keys = '123456'; | ||
6 | +exports.view = { | ||
7 | + mapping: { | ||
8 | + '.js': 'assets', | ||
9 | + '.jsx': 'assets', | ||
10 | + }, | ||
11 | +}; | ||
12 | +exports.assets = { | ||
13 | + devServer: { | ||
14 | + waitStart: true, | ||
15 | + autoPort: true, | ||
16 | + port: 8000, | ||
17 | + command: 'node ' + path.join(__dirname, '../../mocktool/server.js') + ' {port}', | ||
18 | + env: { | ||
19 | + SOCKET_SERVER: 'http://127.0.0.1:{port}', | ||
20 | + }, | ||
21 | + debug: true, | ||
22 | + }, | ||
23 | +}; |
1 | +'use strict'; | ||
2 | + | ||
3 | +const Controller = require('egg').Controller; | ||
4 | + | ||
5 | +class HomeController extends Controller { | ||
6 | + async index() { | ||
7 | + await this.ctx.render('index.js'); | ||
8 | + } | ||
9 | + | ||
10 | + async port() { | ||
11 | + this.ctx.body = this.app.config.assets.devServer.port; | ||
12 | + } | ||
13 | +} | ||
14 | + | ||
15 | +module.exports = HomeController; |
test/fixtures/apps/autoport/app/router.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const path = require('path'); | ||
4 | + | ||
5 | +exports.keys = '123456'; | ||
6 | +exports.view = { | ||
7 | + mapping: { | ||
8 | + '.js': 'assets', | ||
9 | + '.jsx': 'assets', | ||
10 | + }, | ||
11 | +}; | ||
12 | +exports.assets = { | ||
13 | + devServer: { | ||
14 | + waitStart: true, | ||
15 | + autoPort: true, | ||
16 | + command: 'node ' + path.join(__dirname, '../../mocktool/server.js') + ' {port}', | ||
17 | + env: { | ||
18 | + SOCKET_SERVER: 'http://127.0.0.1:{port}', | ||
19 | + }, | ||
20 | + debug: true, | ||
21 | + }, | ||
22 | +}; |
test/fixtures/apps/autoport/package.json
0 → 100644
1 | +body { color: white; } |
1 | +'use strict'; | ||
2 | + | ||
3 | +const path = require('path'); | ||
4 | + | ||
5 | +exports.keys = '123456'; | ||
6 | +exports.view = { | ||
7 | + mapping: { | ||
8 | + '.js': 'assets', | ||
9 | + '.jsx': 'assets', | ||
10 | + }, | ||
11 | +}; | ||
12 | +exports.assets = { | ||
13 | + publicPath: '/public/', | ||
14 | + templateViewEngine: 'nunjucks', | ||
15 | + templatePath: path.join(__dirname, '../app/view/layout.html'), | ||
16 | +}; |
1 | +body { color: white; } |
test/fixtures/apps/crossorigin/app/router.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const path = require('path'); | ||
4 | + | ||
5 | +exports.keys = '123456'; | ||
6 | +exports.view = { | ||
7 | + mapping: { | ||
8 | + '.js': 'assets', | ||
9 | + }, | ||
10 | +}; | ||
11 | +exports.assets = { | ||
12 | + publicPath: '/public/', | ||
13 | + url: 'http://example.com', | ||
14 | + crossorigin: true, | ||
15 | + templateViewEngine: 'nunjucks', | ||
16 | + templatePath: path.join(__dirname, '../app/view/layout.html'), | ||
17 | +}; |
test/fixtures/apps/crossorigin/package.json
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const Controller = require('egg').Controller; | ||
4 | + | ||
5 | +class HomeController extends Controller { | ||
6 | + async index() { | ||
7 | + await this.ctx.render('index.js'); | ||
8 | + } | ||
9 | + | ||
10 | + async context() { | ||
11 | + await this.ctx.render('index.js', { | ||
12 | + data: 1, | ||
13 | + }); | ||
14 | + } | ||
15 | + | ||
16 | +} | ||
17 | + | ||
18 | +module.exports = HomeController; |
1 | +'use strict'; | ||
2 | + | ||
3 | +const path = require('path'); | ||
4 | + | ||
5 | +exports.keys = '123456'; | ||
6 | +exports.view = { | ||
7 | + mapping: { | ||
8 | + '.js': 'assets', | ||
9 | + }, | ||
10 | +}; | ||
11 | +exports.assets = { | ||
12 | + devServer: { | ||
13 | + waitStart: true, | ||
14 | + command: 'node ' + path.join(__dirname, '../../mocktool/server.js'), | ||
15 | + port: 8000, | ||
16 | + env: {}, | ||
17 | + debug: true, | ||
18 | + }, | ||
19 | +}; |
1 | +{ | ||
2 | + "0.a94eff34.async.js": "0.a94eff34.async.js", | ||
3 | + "1.0647e11d.async.js": "1.0647e11d.async.js", | ||
4 | + "2.cbbca76b.async.js": "2.cbbca76b.async.js", | ||
5 | + "constants.js": "constants.e5eb201e.js", | ||
6 | + "index.css": "index.b8e2efea.css", | ||
7 | + "index.js": "index.c4ae6394.js", | ||
8 | + "static/yay.jpg": "static/yay.44dd3333.jpg" | ||
9 | +} |
1 | +'use strict'; | ||
2 | + | ||
3 | +const path = require('path'); | ||
4 | + | ||
5 | +exports.keys = '123456'; | ||
6 | +exports.view = { | ||
7 | + mapping: { | ||
8 | + '.js': 'assets', | ||
9 | + }, | ||
10 | +}; | ||
11 | +exports.assets = { | ||
12 | + contextKey: '__context__', | ||
13 | + devServer: { | ||
14 | + waitStart: true, | ||
15 | + command: 'node ' + path.join(__dirname, '../../mocktool/server.js'), | ||
16 | + port: 8000, | ||
17 | + env: {}, | ||
18 | + debug: true, | ||
19 | + }, | ||
20 | +}; |
请
注册
或
登录
后发表评论