正在显示
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; } | |
\ No newline at end of file | ... | ... |
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; } | |
\ No newline at end of file | ... | ... |
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 | +}; | ... | ... |
请
注册
或
登录
后发表评论