Skip to content

Commit 7b39bed

Browse files
committed
refactor: use better modern mode and cors implementation
BREAKING CHANGE: The `corsUseCredentials` option has been replaced by the new `crossorigin` option.
1 parent c4843ef commit 7b39bed

File tree

14 files changed

+101
-57
lines changed

14 files changed

+101
-57
lines changed

docs/config/README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,16 @@ module.exports = {
162162

163163
Setting this to `false` can speed up production builds if you don't need source maps for production.
164164

165-
### corsUseCredentials
165+
### crossorigin
166166

167-
- Type: `boolean`
168-
- Default: `false`
167+
- Type: `string`
168+
- Default: `undefined`
169+
170+
Configure the `crossorigin` attribute on `<link rel="stylesheet">` and `<script>` tags in generated HTML.
171+
172+
Note that this only affects tags injected by `html-webpack-plugin` - tags directly added in the source template (`public/index.html`) are not affected.
169173

170-
In modern mode, the generated HTML will include `<script type="module">`, which is [loaded with CORS always enabled](https://jakearchibald.com/2017/es-modules-in-browsers/#always-cors). By default, it is treated as `crossorigin="anonymous"`, setting this option to `true` will use `crossorigin="use-credentials"` instead.
174+
See also: [CROS setting attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes)
171175

172176
### configureWebpack
173177

docs/guide/browser-compatibility.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ The cool part though is that there are no special deployment requirements. The g
6363
For a Hello World app, the modern bundle is already 16% smaller. In production, the modern bundle will typically result in significantly faster parsing and evaluation, improving your app's loading performance.
6464
6565
::: tip
66-
`<script type="module">` is loaded [with CORS always enabled](https://jakearchibald.com/2017/es-modules-in-browsers/#always-cors). This means your server must return valid CORS headers such as `Access-Control-Allow-Origin: *`. If you want to fetch the scripts with credentials, use the [corsUseCredentials](../config/#corsusecredentials) option.
66+
`<script type="module">` is loaded [with CORS always enabled](https://jakearchibald.com/2017/es-modules-in-browsers/#always-cors). This means your server must return valid CORS headers such as `Access-Control-Allow-Origin: *`. If you want to fetch the scripts with credentials, set the [crossorigin](../config/#crossorigin) option to `use-credentials`.
6767
6868
Also, modern mode uses an inline script to avoid Safari 10 loading both bundles, so if you are using a strict CSP, you will need to explicitly allow the inline script with:
6969

docs/zh/config/README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,16 @@ module.exports = {
160160

161161
如果你不需要生产环境的 source map,可以将其设置为 `false` 以加速生产环境构建。
162162

163-
### corsUseCredentials
163+
### crossorigin
164164

165-
- Type: `boolean`
166-
- Default: `false`
165+
- Type: `string`
166+
- Default: `undefined`
167+
168+
设置生成的 HTML 中 `<link rel="stylesheet">``<script>` 标签的 `crossorigin` 属性。
169+
170+
需要注意的是该选项仅影响由 `html-webpack-plugin` 在构建时注入的标签 - 直接写在模版 (`public/index.html`) 中的标签不受影响。
167171

168-
在现代模式下,生成的 HTML 会包含 `<script type="module">`,这需要[始终开启 CORS 才能被载入](https://jakearchibald.com/2017/es-modules-in-browsers/#always-cors)。默认情况下,它被处理为 `crossorigin="anonymous"`,将这个选项设置为 `true` 后则会换用 `crossorigin="use-credentials"`
172+
更多细节可查阅: [CROS setting attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes)
169173

170174
### configureWebpack
171175

docs/zh/guide/browser-compatibility.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Vue CLI 会产生两个应用的版本:一个现代版的包,面向支持 [E
6363
对于一个 Hello World 应用来说,现代版的包已经小了 16%。在生产环境下,现代版的包通常都会表现出显著的解析速度和运算速度,从而改善应用的加载性能。
6464

6565
::: tip 提示
66-
`<script type="module">` [需要配合始终开启的 CORS 进行加载](https://jakearchibald.com/2017/es-modules-in-browsers/#always-cors)。这意味着你的服务器必须返回诸如 `Access-Control-Allow-Origin: *` 的有效的 CORS 头。如果你想要通过认证来获取脚本,可使用 [corsUseCredentials](../config/#corsusecredentials) 选项
66+
`<script type="module">` [需要配合始终开启的 CORS 进行加载](https://jakearchibald.com/2017/es-modules-in-browsers/#always-cors)。这意味着你的服务器必须返回诸如 `Access-Control-Allow-Origin: *` 的有效的 CORS 头。如果你想要通过认证来获取脚本,可使将 [crossorigin](../config/#crossorigin) 选项设置为 `use-credentials`
6767

6868
同时,现代浏览器使用一段内联脚本来避免 Safari 10 重复加载脚本包,所以如果你在使用一套严格的 CSP,你需要这样显性地允许内联脚本:
6969

packages/@vue/cli-service/__tests__/build.spec.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ test('build', async () => {
2525

2626
const index = await project.read('dist/index.html')
2727
// should split and preload app.js & vendor.js
28-
expect(index).toMatch(/<link [^>]+js\/app[^>]+\.js rel=preload>/)
29-
expect(index).toMatch(/<link [^>]+js\/chunk-vendors[^>]+\.js rel=preload>/)
28+
expect(index).toMatch(/<link [^>]+js\/app[^>]+\.js rel=preload as=script>/)
29+
expect(index).toMatch(/<link [^>]+js\/chunk-vendors[^>]+\.js rel=preload as=script>/)
3030
// should preload css
31-
expect(index).toMatch(/<link [^>]+app[^>]+\.css rel=preload>/)
31+
expect(index).toMatch(/<link [^>]+app[^>]+\.css rel=preload as=style>/)
3232

3333
// should reference favicon with correct base URL
3434
expect(index).toMatch(/<link rel=icon href=\/favicon.ico>/)
@@ -55,6 +55,10 @@ test('build', async () => {
5555
})
5656

5757
afterAll(async () => {
58-
await browser.close()
59-
server.close()
58+
if (browser) {
59+
await browser.close()
60+
}
61+
if (server) {
62+
server.close()
63+
}
6064
})

packages/@vue/cli-service/__tests__/modernMode.spec.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ test('modern mode', async () => {
3030
expect(index).toMatch(/<script type=module src=\/js\/app\.\w{8}\.js>/)
3131

3232
// should use <link rel="modulepreload" crossorigin=use-credentials> for modern bundle
33-
expect(index).toMatch(/<link [^>]*js\/chunk-vendors\.\w{8}\.js rel=modulepreload>/)
34-
expect(index).toMatch(/<link [^>]*js\/app\.\w{8}\.js rel=modulepreload>/)
33+
expect(index).toMatch(/<link [^>]*js\/chunk-vendors\.\w{8}\.js rel=modulepreload as=script>/)
34+
expect(index).toMatch(/<link [^>]*js\/app\.\w{8}\.js rel=modulepreload as=script>/)
3535

3636
// should use <script nomodule> for legacy bundle
3737
expect(index).toMatch(/<script src=\/js\/chunk-vendors-legacy\.\w{8}\.js nomodule>/)
@@ -41,17 +41,17 @@ test('modern mode', async () => {
4141
const { safariFix } = require('../lib/webpack/ModernModePlugin')
4242
expect(index).toMatch(`<script>${safariFix}</script>`)
4343

44-
// Test corsUseCredentials
45-
await project.write('vue.config.js', `module.exports = { corsUseCredentials: true }`)
44+
// Test crossorigin="use-credentials"
45+
await project.write('vue.config.js', `module.exports = { crossorigin: 'use-credentials' }`)
4646
const { stdout: stdout2 } = await project.run('vue-cli-service build --modern')
4747
expect(stdout2).toMatch('Build complete.')
4848
const index2 = await project.read('dist/index.html')
4949
// should use <script type="module" crossorigin=use-credentials> for modern bundle
5050
expect(index2).toMatch(/<script type=module src=\/js\/chunk-vendors\.\w{8}\.js crossorigin=use-credentials>/)
5151
expect(index2).toMatch(/<script type=module src=\/js\/app\.\w{8}\.js crossorigin=use-credentials>/)
5252
// should use <link rel="modulepreload" crossorigin=use-credentials> for modern bundle
53-
expect(index2).toMatch(/<link [^>]*js\/chunk-vendors\.\w{8}\.js rel=modulepreload crossorigin=use-credentials>/)
54-
expect(index2).toMatch(/<link [^>]*js\/app\.\w{8}\.js rel=modulepreload crossorigin=use-credentials>/)
53+
expect(index2).toMatch(/<link [^>]*js\/chunk-vendors\.\w{8}\.js rel=modulepreload as=script crossorigin=use-credentials>/)
54+
expect(index2).toMatch(/<link [^>]*js\/app\.\w{8}\.js rel=modulepreload as=script crossorigin=use-credentials>/)
5555

5656
// start server and ensure the page loads properly
5757
const port = await portfinder.getPortPromise()

packages/@vue/cli-service/__tests__/multiPage.spec.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,10 @@ test('build w/ multi page', async () => {
8181

8282
const assertSharedAssets = file => {
8383
// should split and preload vendor chunk
84-
expect(file).toMatch(/<link [^>]*js\/chunk-vendors[^>]*\.js rel=preload>/)
84+
expect(file).toMatch(/<link [^>]*js\/chunk-vendors[^>]*\.js rel=preload as=script>/)
8585
// should split and preload common js and css
86-
expect(file).toMatch(/<link [^>]*js\/chunk-common[^>]*\.js rel=preload>/)
87-
expect(file).toMatch(/<link [^>]*chunk-common[^>]*\.css rel=preload>/)
86+
expect(file).toMatch(/<link [^>]*js\/chunk-common[^>]*\.js rel=preload as=script>/)
87+
expect(file).toMatch(/<link [^>]*chunk-common[^>]*\.css rel=preload as=style>/)
8888
// should load common css
8989
expect(file).toMatch(/<link href=\/css\/chunk-common\.\w+\.css rel=stylesheet>/)
9090
// should load common js
@@ -95,9 +95,9 @@ test('build w/ multi page', async () => {
9595
const index = await project.read('dist/index.html')
9696
assertSharedAssets(index)
9797
// should preload correct page file
98-
expect(index).toMatch(/<link [^>]*js\/index[^>]*\.js rel=preload>/)
99-
expect(index).not.toMatch(/<link [^>]*js\/foo[^>]*\.js rel=preload>/)
100-
expect(index).not.toMatch(/<link [^>]*js\/bar[^>]*\.js rel=preload>/)
98+
expect(index).toMatch(/<link [^>]*js\/index[^>]*\.js rel=preload as=script>/)
99+
expect(index).not.toMatch(/<link [^>]*js\/foo[^>]*\.js rel=preload as=script>/)
100+
expect(index).not.toMatch(/<link [^>]*js\/bar[^>]*\.js rel=preload as=script>/)
101101
// should prefetch async chunk js and css
102102
expect(index).toMatch(/<link [^>]*css\/chunk-\w+\.\w+\.css rel=prefetch>/)
103103
expect(index).toMatch(/<link [^>]*js\/chunk-\w+\.\w+\.js rel=prefetch>/)
@@ -109,9 +109,9 @@ test('build w/ multi page', async () => {
109109
const foo = await project.read('dist/foo.html')
110110
assertSharedAssets(foo)
111111
// should preload correct page file
112-
expect(foo).not.toMatch(/<link [^>]*js\/index[^>]*\.js rel=preload>/)
113-
expect(foo).toMatch(/<link [^>]*js\/foo[^>]*\.js rel=preload>/)
114-
expect(foo).not.toMatch(/<link [^>]*js\/bar[^>]*\.js rel=preload>/)
112+
expect(foo).not.toMatch(/<link [^>]*js\/index[^>]*\.js rel=preload as=script>/)
113+
expect(foo).toMatch(/<link [^>]*js\/foo[^>]*\.js rel=preload as=script>/)
114+
expect(foo).not.toMatch(/<link [^>]*js\/bar[^>]*\.js rel=preload as=script>/)
115115
// should not prefetch async chunk js and css because it's not used by
116116
// this entry
117117
expect(foo).not.toMatch(/<link [^>]*css\/chunk-\w+\.\w+\.css rel=prefetch>/)
@@ -124,9 +124,9 @@ test('build w/ multi page', async () => {
124124
const bar = await project.read('dist/bar.html')
125125
assertSharedAssets(bar)
126126
// should preload correct page file
127-
expect(bar).not.toMatch(/<link [^>]*js\/index[^>]*\.js rel=preload>/)
128-
expect(bar).not.toMatch(/<link [^>]*js\/foo[^>]*\.js rel=preload>/)
129-
expect(bar).toMatch(/<link [^>]*js\/bar[^>]*\.js rel=preload>/)
127+
expect(bar).not.toMatch(/<link [^>]*js\/index[^>]*\.js rel=preload as=script>/)
128+
expect(bar).not.toMatch(/<link [^>]*js\/foo[^>]*\.js rel=preload as=script>/)
129+
expect(bar).toMatch(/<link [^>]*js\/bar[^>]*\.js rel=preload as=script>/)
130130
// should prefetch async chunk js and css
131131
expect(bar).toMatch(/<link [^>]*css\/chunk-\w+\.\w+\.css rel=prefetch>/)
132132
expect(bar).toMatch(/<link [^>]*js\/chunk-\w+\.\w+\.js rel=prefetch>/)

packages/@vue/cli-service/lib/commands/build/resolveAppConfig.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
module.exports = (api, args, options) => {
22
const config = api.resolveChainableWebpackConfig()
33
const targetDir = api.resolve(args.dest || options.outputDir)
4-
const { corsUseCredentials } = options
54

65
// respect inline build destination in copy plugin
76
if (args.dest && config.plugins.has('copy')) {
@@ -19,7 +18,6 @@ module.exports = (api, args, options) => {
1918
.plugin('modern-mode-legacy')
2019
.use(ModernModePlugin, [{
2120
targetDir,
22-
corsUseCredentials,
2321
isModernBuild: false
2422
}])
2523
} else {
@@ -28,7 +26,6 @@ module.exports = (api, args, options) => {
2826
.plugin('modern-mode-modern')
2927
.use(ModernModePlugin, [{
3028
targetDir,
31-
corsUseCredentials,
3229
isModernBuild: true
3330
}])
3431
}

packages/@vue/cli-service/lib/config/app.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,16 @@ module.exports = (api, options) => {
250250
}
251251
}
252252

253+
// CORS and Subresource Integrity
254+
if (options.crossorigin != null || options.integreity) {
255+
webpackConfig
256+
.plugin('cors')
257+
.use(require('../webpack/CorsPlugin'), [{
258+
crossorigin: options.crossorigin,
259+
integreity: options.integreity
260+
}])
261+
}
262+
253263
// copy static assets in public/
254264
const publicDir = api.resolve('public')
255265
if (!isLegacyBundle && fs.existsSync(publicDir)) {

packages/@vue/cli-service/lib/options.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const schema = createSchema(joi => joi.object({
1212
parallel: joi.boolean(),
1313
devServer: joi.object(),
1414
pages: joi.object(),
15-
corsUseCredentials: joi.boolean(),
15+
crossorigin: joi.string().valid(['', 'anonymous', 'use-credentials']),
1616

1717
// css
1818
css: joi.object({
@@ -91,8 +91,11 @@ exports.defaults = () => ({
9191
pages: undefined,
9292

9393
// <script type="module" crossorigin="use-credentials">
94-
// #1656, #1867
95-
corsUseCredentials: false,
94+
// #1656, #1867, #2025
95+
crossorigin: undefined,
96+
97+
// subresource integrity
98+
integreity: false,
9699

97100
css: {
98101
// extract: true,

0 commit comments

Comments
 (0)