Skip to content

Commit 89b2812

Browse files
ikobesorrycc
andcommitted
feat: simple way to external in umi (umijs#2287)
* feat: 更简单的 external 方案 * code style * lock version * fix ci * refact getExternalData.ts for test * feat: add auto external tests * remove duplicated umi-types Co-authored-by: chencheng (云谦) <sorrycc@gmail.com>
1 parent 87c999c commit 89b2812

19 files changed

Lines changed: 1286 additions & 9 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"private": true,
33
"scripts": {
44
"bootstrap": "lerna bootstrap",
5-
"build": "umi-tools build && umi-tools rollup -g dva:dva,antd:antd",
5+
"build": "umi-tools build",
66
"changelog": "lerna-changelog",
77
"chore:update-deps": "./scripts/update_deps.sh",
88
"test": "node scripts/test.js",

packages/umi-build-dev/src/getPlugins.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default function(opts = {}) {
2121
'./plugins/commands/rm',
2222
'./plugins/commands/config',
2323
'./plugins/commands/block',
24-
'./plugins/commands/ui',
24+
// './plugins/commands/ui',
2525
'./plugins/commands/version',
2626
'./plugins/global-js',
2727
'./plugins/global-css',
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# umi-plugin-auto-externals
2+
3+
A simple way to external in umi.
4+
5+
## Usage
6+
7+
Configured in `.umirc.js`:
8+
9+
```js
10+
export default {
11+
plugins: [
12+
['umi-plugin-auto-externals', {
13+
packages: [ 'antd' ],
14+
urlTemplate: 'https://unpkg.com/{{ library }}@{{ version }}/{{ path }}',
15+
checkOnline: false,
16+
}],
17+
],
18+
};
19+
```
20+
21+
## Configuration items
22+
23+
### packages
24+
25+
* Type: `Array`
26+
27+
Librarys need to be externaled. Support librarys are:
28+
29+
* `react`
30+
* `react-dom`
31+
* `moment`
32+
* `antd`
33+
* `jquery`
34+
35+
https://github.com/umijs/auto-external-packages
36+
37+
### urlTemplate
38+
39+
* Type: `String`
40+
41+
If you want to use your own CDN service, you need to config this item.
42+
43+
There will be three variables provided to render this template: library, version, path.
44+
45+
For example: `https://unpkg.com/{{ library }}@{{ version }}/{{ path }}`
46+
47+
### checkOnline
48+
49+
* Type: `Boolean`
50+
51+
If checkOnline is true, we will check all urls if online.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "umi-plugin-auto-externals",
3+
"version": "1.0.0",
4+
"main": "./lib/index.js",
5+
"repository": {
6+
"type": "git",
7+
"url": "https://github.com/umijs/umi/tree/master/packages/umi-plugin-auto-externals"
8+
},
9+
"homepage": "https://github.com/umijs/umi/tree/master/packages/umi-plugin-auto-externals",
10+
"authors": [
11+
"ikobe <ikobe621@gmail.com> (https://github.com/ikobe)"
12+
],
13+
"bugs": {
14+
"url": "https://github.com/umijs/umi/issues"
15+
},
16+
"license": "MIT",
17+
"files": [
18+
"lib",
19+
"src"
20+
],
21+
"dependencies": {
22+
"auto-external-packages": "^1.0.0",
23+
"semver": "6.0.0",
24+
"urllib": "2.33.3"
25+
},
26+
"devDependencies": {
27+
"umi-types": "0.3.2"
28+
}
29+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { IApi } from 'umi-types';
2+
import { join } from 'path';
3+
import { IOpts } from './types';
4+
import { getExternalData, onlineCheck } from './util';
5+
6+
export default function(
7+
api: IApi,
8+
{
9+
packages = [],
10+
urlTemplate = 'https://unpkg.com/{{ library }}@{{ version }}/{{ path }}',
11+
checkOnline = false,
12+
}: IOpts,
13+
) {
14+
const { debug, cwd, config } = api;
15+
const configs = getExternalData({
16+
pkg: require(join(cwd, 'package.json')),
17+
versionInfos: api.applyPlugins('addVersionInfo'),
18+
config,
19+
packages,
20+
urlTemplate,
21+
});
22+
23+
debug('User external data:');
24+
debug(JSON.stringify(configs));
25+
26+
if (!configs.length) {
27+
return;
28+
}
29+
30+
if (checkOnline) {
31+
api.onStartAsync(async () => {
32+
await onlineCheck(configs);
33+
});
34+
}
35+
36+
// 修改 webpack external 配置
37+
api.chainWebpackConfig(webpackConfig => {
38+
webpackConfig.externals(getWebpackExternalConfig(configs));
39+
});
40+
41+
// 修改 script 标签
42+
const scripts = configs.reduce(
43+
(accumulator, current) => accumulator.concat(current.scripts),
44+
[],
45+
);
46+
api.addHTMLHeadScript(() => scripts.map(src => ({ src, crossorigin: true })));
47+
48+
// 添加 style
49+
const styles = configs.reduce(
50+
(accumulator, current) => accumulator.concat(current.styles),
51+
[],
52+
);
53+
api.addHTMLLink(() =>
54+
styles.map(href => ({
55+
rel: 'stylesheet',
56+
href,
57+
})),
58+
);
59+
60+
// TODO: 传递 exclude
61+
}
62+
63+
function getWebpackExternalConfig(configs) {
64+
const res = [];
65+
const objectConfig = {};
66+
configs.forEach(({ key, global }) => {
67+
if (typeof global === 'string') {
68+
objectConfig[key] = global;
69+
} else {
70+
res.push(global);
71+
}
72+
});
73+
res.unshift(objectConfig);
74+
return res;
75+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// 用户配置参数
2+
export interface IOpts {
3+
packages: string[] | Boolean;
4+
urlTemplate?: string;
5+
checkOnline?: boolean;
6+
}
7+
8+
export interface IExternalData {
9+
key: string;
10+
global: any;
11+
polyfillExclude: string[];
12+
scripts: string[];
13+
styles: string[];
14+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import chalk from 'chalk';
2+
3+
export default function error(msg: string, name?: string) {
4+
const err = new Error(chalk.red(msg));
5+
err.name = name || 'AutoExternalError';
6+
throw err;
7+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import getExternalData from './getExternalData';
2+
3+
test('empty', () => {
4+
const configs = getExternalData({
5+
pkg: {},
6+
config: {},
7+
versionInfos: [],
8+
packages: [],
9+
});
10+
expect(configs).toEqual([]);
11+
});
12+
13+
test('boolean', () => {
14+
const configs = getExternalData({
15+
pkg: {
16+
dependencies: {
17+
moment: '2.24.0',
18+
antd: '3.16.3',
19+
jquery: '3',
20+
},
21+
},
22+
config: {
23+
externals: {},
24+
},
25+
versionInfos: [
26+
'react@16.6.3 (react@16.6.3@react)',
27+
'react-dom@16.6.3 (react-dom@16.6.3@react-dom)',
28+
],
29+
packages: true,
30+
});
31+
const keys = configs.map(item => item.key);
32+
expect(keys).toEqual(['react', 'react-dom', 'moment', 'antd', 'jquery']);
33+
});
34+
35+
test('external antd', () => {
36+
const configs = getExternalData({
37+
pkg: {
38+
dependencies: {
39+
moment: '2.24.0',
40+
antd: '3.16.3',
41+
jquery: '3',
42+
},
43+
},
44+
config: {
45+
externals: {},
46+
},
47+
versionInfos: [
48+
'react@16.6.3 (react@16.6.3@react)',
49+
'react-dom@16.6.3 (react-dom@16.6.3@react-dom)',
50+
],
51+
packages: ['antd'],
52+
});
53+
const keys = configs.map(item => item.key);
54+
expect(keys).toEqual(['react', 'react-dom', 'moment', 'antd']);
55+
});
56+
57+
test('external react', () => {
58+
const configs = getExternalData({
59+
pkg: {
60+
dependencies: {
61+
moment: '2.24.0',
62+
antd: '3.16.3',
63+
jquery: '3',
64+
},
65+
},
66+
config: {
67+
externals: {},
68+
},
69+
versionInfos: [
70+
'react@16.6.3 (react@16.6.3@react)',
71+
'react-dom@16.6.3 (react-dom@16.6.3@react-dom)',
72+
],
73+
packages: ['react'],
74+
});
75+
const keys = configs.map(item => item.key);
76+
expect(keys).toEqual(['react']);
77+
});
78+
79+
test('external react-dom', () => {
80+
const configs = getExternalData({
81+
pkg: {
82+
dependencies: {
83+
moment: '2.24.0',
84+
antd: '3.16.3',
85+
jquery: '3',
86+
},
87+
},
88+
config: {
89+
externals: {},
90+
},
91+
versionInfos: [
92+
'react@16.6.3 (react@16.6.3@react)',
93+
'react-dom@16.6.3 (react-dom@16.6.3@react-dom)',
94+
],
95+
packages: ['react-dom'],
96+
});
97+
const keys = configs.map(item => item.key);
98+
expect(keys).toEqual(['react', 'react-dom']);
99+
});
100+
101+
test('auto external conflict with externals config', () => {
102+
try {
103+
getExternalData({
104+
pkg: {
105+
dependencies: {
106+
moment: '2.24.0',
107+
antd: '3.16.3',
108+
jquery: '3',
109+
},
110+
},
111+
config: {
112+
externals: {
113+
react: '16',
114+
},
115+
},
116+
versionInfos: [
117+
'react@16.6.3 (react@16.6.3@react)',
118+
'react-dom@16.6.3 (react-dom@16.6.3@react-dom)',
119+
],
120+
packages: ['react-dom'],
121+
});
122+
throw new Error('will not throw');
123+
} catch (e) {
124+
expect(e.message).toMatch(/react is is both in external and autoExternals/);
125+
}
126+
});
127+
128+
test('moment not in dependencies', () => {
129+
try {
130+
getExternalData({
131+
pkg: {},
132+
config: {},
133+
versionInfos: [
134+
'react@16.6.3 (react@16.6.3@react)',
135+
'react-dom@16.6.3 (react-dom@16.6.3@react-dom)',
136+
],
137+
packages: ['moment'],
138+
});
139+
throw new Error('will not throw');
140+
} catch (e) {
141+
expect(e.message).toMatch(/Can not find dependencies\(moment\) version/);
142+
}
143+
});

0 commit comments

Comments
 (0)