From 2726dca2992690c28dc2ab36eccde56f1eb8b0ea Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Thu, 8 Apr 2021 17:04:19 +0800 Subject: [PATCH 01/10] test: add test for skipping safari nomodule fix injection --- .../cli-service/__tests__/modernMode.spec.js | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/packages/@vue/cli-service/__tests__/modernMode.spec.js b/packages/@vue/cli-service/__tests__/modernMode.spec.js index b74d9fec8f..f509b692d4 100644 --- a/packages/@vue/cli-service/__tests__/modernMode.spec.js +++ b/packages/@vue/cli-service/__tests__/modernMode.spec.js @@ -43,10 +43,6 @@ test('modern mode', async () => { expect(index).toMatch(/`) - // Test crossorigin="use-credentials" await project.write('vue.config.js', `module.exports = { crossorigin: 'use-credentials' }`) const { stdout: stdout2 } = await project.run('vue-cli-service build --modern') @@ -82,18 +78,42 @@ test('modern mode', async () => { expect(await getH1Text()).toMatch('Welcome to Your Vue.js App') }) -test('no-unsafe-inline', async () => { - const project = await create('no-unsafe-inline', defaultPreset) +test('should not inject the nomodule-fix script if Safari 10 is not targeted', async () => { + // the default targets already excludes safari 10 + const project = await create('skip-safari-fix', defaultPreset) - const { stdout } = await project.run('vue-cli-service build --modern --no-unsafe-inline') + const { stdout } = await project.run('vue-cli-service build --modern') expect(stdout).toMatch('Build complete.') + // should contain no inline scripts in the output html + const index = await project.read('dist/index.html') + expect(index).not.toMatch(/[^>]\s*<\/script>/) + // should not contain the safari-nomodule-fix bundle, either + const files = await fs.readdir(path.join(project.dir, 'dist/js')) + expect(files.some(f => /^safari-nomodule-fix\.js$/.test(f))).toBe(false) +}) + +test('should inject nomodule-fix script when Safari 10 support is required', async () => { + const project = await create('safari-nomodule-fix', defaultPreset) + + const pkg = JSON.parse(await project.read('package.json')) + pkg.browserslist.push('safari > 10') + await project.write('package.json', JSON.stringify(pkg, null, 2)) + + let { stdout } = await project.run('vue-cli-service build --modern') + let index = await project.read('dist/index.html') + // should inject Safari 10 nomodule fix as an inline script + const { safariFix } = require('../lib/webpack/ModernModePlugin') + expect(index).toMatch(``) + + // `--no-unsafe-inline` option + stdout = (await project.run('vue-cli-service build --modern --no-unsafe-inline')).stdout + expect(stdout).toMatch('Build complete.') // should output a separate safari-nomodule-fix bundle const files = await fs.readdir(path.join(project.dir, 'dist/js')) expect(files.some(f => /^safari-nomodule-fix\.js$/.test(f))).toBe(true) - // should contain no inline scripts in the output html - const index = await project.read('dist/index.html') + index = await project.read('dist/index.html') expect(index).not.toMatch(/[^>]\s*<\/script>/) }) From 1b21eb271cec98aee25551a535ed700ddfe44ae9 Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Thu, 8 Apr 2021 23:13:54 +0800 Subject: [PATCH 02/10] feat: only inject safari-nomodule-fix when necessary --- packages/@vue/cli-service/lib/util/targets.js | 50 ++++++++++++++++ .../lib/webpack/ModernModePlugin.js | 60 +++++++++++-------- packages/@vue/cli-service/package.json | 1 + 3 files changed, 87 insertions(+), 24 deletions(-) create mode 100644 packages/@vue/cli-service/lib/util/targets.js diff --git a/packages/@vue/cli-service/lib/util/targets.js b/packages/@vue/cli-service/lib/util/targets.js new file mode 100644 index 0000000000..bd2ce6b20c --- /dev/null +++ b/packages/@vue/cli-service/lib/util/targets.js @@ -0,0 +1,50 @@ +// copied from @vue/babel-preset-app + +const { semver } = require('@vue/cli-shared-utils') +const { default: getTargets } = require('@babel/helper-compilation-targets') + +const allModernTargets = getTargets( + { esmodules: true }, + { ignoreBrowserslistConfig: true } +) + +function getIntersectionTargets (targets, constraintTargets) { + const intersection = Object.keys(constraintTargets).reduce( + (results, browser) => { + // exclude the browsers that the user does not need + if (!targets[browser]) { + return results + } + + // if the user-specified version is higher the minimum version that supports esmodule, than use it + results[browser] = semver.gt( + semver.coerce(constraintTargets[browser]), + semver.coerce(targets[browser]) + ) + ? constraintTargets[browser] + : targets[browser] + + return results + }, + {} + ) + + return intersection +} + +function getModernTargets (targets) { + // use the intersection of modern mode browsers and user defined targets config + return getIntersectionTargets(targets, allModernTargets) +} + +const projectTargets = getTargets() +const projectModernTargets = getModernTargets(projectTargets) + +module.exports = { + getTargets, + getModernTargets, + getIntersectionTargets, + + projectTargets, + projectModernTargets +} diff --git a/packages/@vue/cli-service/lib/webpack/ModernModePlugin.js b/packages/@vue/cli-service/lib/webpack/ModernModePlugin.js index 3e3d3a7b44..180143465c 100644 --- a/packages/@vue/cli-service/lib/webpack/ModernModePlugin.js +++ b/packages/@vue/cli-service/lib/webpack/ModernModePlugin.js @@ -2,6 +2,16 @@ const fs = require('fs-extra') const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') +const { semver } = require('@vue/cli-shared-utils') +const { projectModernTargets } = require('../util/targets') + +const minSafariVersion = projectModernTargets.safari +const minIOSVersion = projectModernTargets.ios +const supportsSafari10 = + (minSafariVersion && semver.lt(semver.coerce(minSafariVersion), '11.0.0')) || + (minIOSVersion && semver.lt(semver.coerce(minIOSVersion), '11.0.0')) +const needsSafariFix = supportsSafari10 + // https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc const safariFix = `!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();` @@ -77,32 +87,34 @@ class ModernModePlugin { .filter(a => a.tagName === 'script' && a.attributes) legacyAssets.forEach(a => { a.attributes.nomodule = '' }) - if (this.unsafeInline) { - // inject inline Safari 10 nomodule fix - tags.push({ - tagName: 'script', - closeTag: true, - innerHTML: safariFix - }) - } else { - // inject the fix as an external script - const safariFixPath = path.join(this.jsDirectory, 'safari-nomodule-fix.js') - const fullSafariFixPath = path.join(compilation.options.output.publicPath, safariFixPath) - compilation.assets[safariFixPath] = { - source: function () { - return Buffer.from(safariFix) - }, - size: function () { - return Buffer.byteLength(safariFix) + if (needsSafariFix) { + if (this.unsafeInline) { + // inject inline Safari 10 nomodule fix + tags.push({ + tagName: 'script', + closeTag: true, + innerHTML: safariFix + }) + } else { + // inject the fix as an external script + const safariFixPath = path.join(this.jsDirectory, 'safari-nomodule-fix.js') + const fullSafariFixPath = path.join(compilation.options.output.publicPath, safariFixPath) + compilation.assets[safariFixPath] = { + source: function () { + return Buffer.from(safariFix) + }, + size: function () { + return Buffer.byteLength(safariFix) + } } + tags.push({ + tagName: 'script', + closeTag: true, + attributes: { + src: fullSafariFixPath + } + }) } - tags.push({ - tagName: 'script', - closeTag: true, - attributes: { - src: fullSafariFixPath - } - }) } tags.push(...legacyAssets) diff --git a/packages/@vue/cli-service/package.json b/packages/@vue/cli-service/package.json index 42ac1247fa..0383b2cc3e 100644 --- a/packages/@vue/cli-service/package.json +++ b/packages/@vue/cli-service/package.json @@ -23,6 +23,7 @@ }, "homepage": "https://cli.vuejs.org/", "dependencies": { + "@babel/helper-compilation-targets": "^7.12.16", "@soda/friendly-errors-webpack-plugin": "^1.8.0", "@soda/get-current-script": "^1.0.2", "@types/minimist": "^1.2.0", From b079973dd7dd8787aa9ee442f28f6c7d13bd908d Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Fri, 9 Apr 2021 16:06:44 +0800 Subject: [PATCH 03/10] feat: modern mode on by default, and `--no-module` option `--no-modern` sounds weird, so I prefer the `--no-module` option. But `--no-modern` is kept for the convenience of implementation, could be removed later. --- .../@vue/cli-service/__tests__/build.spec.js | 4 ++-- .../cli-service/__tests__/modernMode.spec.js | 13 ++++++---- .../cli-service/lib/commands/build/index.js | 24 +++++++++++-------- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/packages/@vue/cli-service/__tests__/build.spec.js b/packages/@vue/cli-service/__tests__/build.spec.js index c32e8bffa8..ce94962897 100644 --- a/packages/@vue/cli-service/__tests__/build.spec.js +++ b/packages/@vue/cli-service/__tests__/build.spec.js @@ -38,8 +38,8 @@ test('build', async () => { // expect(index).toMatch(/]+app[^>]+\.css" rel="preload" as="style">/) // should inject scripts - expect(index).toMatch(/`) // `--no-unsafe-inline` option - stdout = (await project.run('vue-cli-service build --modern --no-unsafe-inline')).stdout + stdout = (await project.run('vue-cli-service build --no-unsafe-inline')).stdout expect(stdout).toMatch('Build complete.') // should output a separate safari-nomodule-fix bundle const files = await fs.readdir(path.join(project.dir, 'dist/js')) @@ -117,6 +117,9 @@ test('should inject nomodule-fix script when Safari 10 support is required', asy expect(index).not.toMatch(/[^>]\s*<\/script>/) }) +test.todo('--no-module') +test.todo('--no-modern as an alias to --no-module') + afterAll(async () => { if (browser) { await browser.close() diff --git a/packages/@vue/cli-service/lib/commands/build/index.js b/packages/@vue/cli-service/lib/commands/build/index.js index d749e7230d..84806e9c4e 100644 --- a/packages/@vue/cli-service/lib/commands/build/index.js +++ b/packages/@vue/cli-service/lib/commands/build/index.js @@ -1,6 +1,7 @@ const defaults = { clean: true, target: 'app', + module: true, formats: 'commonjs,umd,umd-min', 'unsafe-inline': true } @@ -26,7 +27,7 @@ module.exports = (api, options) => { options: { '--mode': `specify env mode (default: production)`, '--dest': `specify output directory (default: ${options.outputDir})`, - '--modern': `build app targeting modern browsers with auto fallback`, + '--no-module': `build app without generating