Skip to content

feat!: turn on modern mode by default, and provide a --no-module option #6416

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 14, 2021
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ async function readVendorFile () {
return project.read(`dist/js/${filename}`)
}

async function readLegacyVendorFile () {
const files = await fs.readdir(path.join(project.dir, 'dist/js'))
const filename = files.find(f => /chunk-vendors-legacy\.[^.]+\.js$/.test(f))
return project.read(`dist/js/${filename}`)
}

beforeAll(async () => {
project = await create('babel-transpile-deps', defaultPreset)

Expand All @@ -39,6 +45,8 @@ beforeAll(async () => {
let $packageJson = await project.read('package.json')

$packageJson = JSON.parse($packageJson)
$packageJson.browserslist.push('ie 11') // to ensure arrow function transformation is enabled
$packageJson.browserslist.push('safari 11') // to ensure optional chaining transformation is enabled
$packageJson.dependencies['external-dep'] = '1.0.0'
$packageJson.dependencies['@scope/external-dep'] = '1.0.0'
$packageJson = JSON.stringify($packageJson)
Expand Down Expand Up @@ -70,7 +78,7 @@ afterAll(async () => {

test('dep from node_modules should not been transpiled by default', async () => {
await project.run('vue-cli-service build')
expect(await readVendorFile()).toMatch('() => "__TEST__"')
expect(await readLegacyVendorFile()).toMatch('() => "__TEST__"')
})

test('dep from node_modules should been transpiled when matched by transpileDependencies', async () => {
Expand All @@ -79,9 +87,9 @@ test('dep from node_modules should been transpiled when matched by transpileDepe
`module.exports = { transpileDependencies: ['external-dep', '@scope/external-dep'] }`
)
await project.run('vue-cli-service build')
expect(await readVendorFile()).toMatch('return "__TEST__"')
expect(await readLegacyVendorFile()).toMatch('return "__TEST__"')

expect(await readVendorFile()).toMatch('return "__SCOPE_TEST__"')
expect(await readLegacyVendorFile()).toMatch('return "__SCOPE_TEST__"')
})

test('dep from node_modules should been transpiled when transpileDependencies is true', async () => {
Expand All @@ -90,9 +98,9 @@ test('dep from node_modules should been transpiled when transpileDependencies is
`module.exports = { transpileDependencies: true }`
)
await project.run('vue-cli-service build')
expect(await readVendorFile()).toMatch('return "__TEST__"')
expect(await readLegacyVendorFile()).toMatch('return "__TEST__"')

expect(await readVendorFile()).toMatch('return "__SCOPE_TEST__"')
expect(await readLegacyVendorFile()).toMatch('return "__SCOPE_TEST__"')
})

// https://github.com/vuejs/vue-cli/issues/3057
Expand All @@ -104,6 +112,24 @@ test('only transpile package with same name specified in transpileDependencies',
try {
await project.run('vue-cli-service build')
} catch (e) {}
expect(await readVendorFile()).toMatch('() => "__TEST__"')
expect(await readVendorFile()).toMatch('() => "__SCOPE_TEST__"')
expect(await readLegacyVendorFile()).toMatch('() => "__TEST__"')
expect(await readLegacyVendorFile()).toMatch('() => "__SCOPE_TEST__"')
})

test('when transpileDependencies is on, the module build should also include transpiled code (with a different target)', async () => {
await project.write(
'vue.config.js',
`module.exports = { transpileDependencies: true }`
)
await project.write(
'node_modules/external-dep/index.js',
`const test = (x) => x?.y?.z;\nexport default test`
)

await project.run('vue-cli-service build')
const file = await readVendorFile()
// module build won't need arrow function transformation
expect(file).toMatch('() => "__SCOPE_TEST__"')
// but still needs optional chaining transformation
expect(file).not.toMatch('x?.y?.z')
})
4 changes: 2 additions & 2 deletions packages/@vue/cli-service/__tests__/build.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ test('build', async () => {
// expect(index).toMatch(/<link [^>]+app[^>]+\.css" rel="preload" as="style">/)

// should inject scripts
expect(index).toMatch(/<script defer="defer" src="\/js\/chunk-vendors\.\w{8}\.js">/)
expect(index).toMatch(/<script defer="defer" src="\/js\/app\.\w{8}\.js">/)
expect(index).toMatch(/<script defer="defer" src="\/js\/chunk-vendors-legacy\.\w{8}\.js" nomodule>/)
expect(index).toMatch(/<script defer="defer" src="\/js\/app-legacy\.\w{8}\.js" nomodule>/)
// should inject css
expect(index).toMatch(/<link href="\/css\/app\.\w{8}\.css" rel="stylesheet">/)

Expand Down
4 changes: 2 additions & 2 deletions packages/@vue/cli-service/__tests__/cors.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ test('build', async () => {
// expect(index).toMatch(/<link [^>]+app[^>]+\.css rel=preload as=style crossorigin>/)

// should apply crossorigin and add integrity to scripts and css
expect(index).toMatch(/<script defer="defer" src="\/js\/chunk-vendors\.\w{8}\.js" crossorigin integrity="sha384-.{64}\s?">/)
expect(index).toMatch(/<script defer="defer" src="\/js\/app\.\w{8}\.js" crossorigin integrity="sha384-.{64}\s?">/)
expect(index).toMatch(/<script defer="defer" src="\/js\/chunk-vendors\.\w{8}\.js" crossorigin integrity="sha384-.{64}\s?" type="module">/)
expect(index).toMatch(/<script defer="defer" src="\/js\/app\.\w{8}\.js" crossorigin integrity="sha384-.{64}\s?" type="module">/)
expect(index).toMatch(/<link href="\/css\/app\.\w{8}\.css" rel="stylesheet" crossorigin integrity="sha384-.{64}\s?">/)

// verify integrity is correct by actually running it
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
jest.setTimeout(30000)
jest.setTimeout(300000)

const create = require('@vue/cli-test-utils/createTestProject')
const { defaultPreset } = require('@vue/cli/lib/options')
Expand Down
55 changes: 44 additions & 11 deletions packages/@vue/cli-service/__tests__/modernMode.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ let server, browser
test('modern mode', async () => {
const project = await create('modern-mode', defaultPreset)

const { stdout } = await project.run('vue-cli-service build --modern')
const { stdout } = await project.run('vue-cli-service build')
expect(stdout).toMatch('Build complete.')

// assert correct bundle files
Expand Down Expand Up @@ -43,13 +43,9 @@ test('modern mode', async () => {
expect(index).toMatch(/<script defer="defer" src="\/js\/chunk-vendors-legacy\.\w{8}\.js" nomodule>/)
expect(index).toMatch(/<script defer="defer" src="\/js\/app-legacy\.\w{8}\.js" nomodule>/)

// should inject Safari 10 nomodule fix
const { safariFix } = require('../lib/webpack/ModernModePlugin')
expect(index).toMatch(`<script>${safariFix}</script>`)

// 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')
const { stdout: stdout2 } = await project.run('vue-cli-service build')
expect(stdout2).toMatch('Build complete.')
const index2 = await project.read('dist/index.html')
// should use <script type="module" crossorigin="use-credentials"> for modern bundle
Expand Down Expand Up @@ -82,21 +78,58 @@ 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')
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')
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(`<script>${safariFix}</script>`)

// `--no-unsafe-inline` option
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'))
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>/)
})

test('--no-module', async () => {
const project = await create('no-module', defaultPreset)

const { stdout } = await project.run('vue-cli-service build --no-module')
expect(stdout).toMatch('Build complete.')

const index = await project.read('dist/index.html')
expect(index).not.toMatch('type="module"')

const files = await fs.readdir(path.join(project.dir, 'dist/js'))
expect(files.some(f => /-legacy.js/.test(f))).toBe(false)
})

afterAll(async () => {
if (browser) {
await browser.close()
Expand Down
22 changes: 11 additions & 11 deletions packages/@vue/cli-service/__tests__/multiPage.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,14 @@ test('build w/ multi page', async () => {
const assertSharedAssets = file => {
// should split and preload vendor chunk
// expect(file).toMatch(/<link [^>]*js\/chunk-vendors[^>]*\.js" rel="preload" as="script">/)
expect(file).toMatch(/<script [^>]*src="\/js\/chunk-vendors\.\w+\.js">/)
expect(file).toMatch(/<script [^>]*src="\/js\/chunk-vendors\.\w+\.js" type="module">/)
}

const index = await project.read('dist/index.html')
assertSharedAssets(index)
// should split and preload common js and css
// expect(index).toMatch(/<link [^>]*js\/chunk-common[^>]*\.js" rel="preload" as="script">/)
expect(index).toMatch(/<script [^>]*src="\/js\/chunk-common\.\w+\.js">/)
expect(index).toMatch(/<script [^>]*src="\/js\/chunk-common\.\w+\.js" type="module">/)
expect(index).toMatch(/<link href="\/css\/chunk-common\.\w+\.css" rel="stylesheet">/)
// expect(index).toMatch(/<link [^>]*chunk-common[^>]*\.css" rel="preload" as="style">/)
// should preload correct page file
Expand All @@ -128,9 +128,9 @@ test('build w/ multi page', async () => {
// expect(index).toMatch(/<link [^>]*css\/chunk-\w+\.\w+\.css" rel="prefetch">/)
// expect(index).toMatch(/<link [^>]*js\/chunk-\w+\.\w+\.js" rel="prefetch">/)
// should load correct page js
expect(index).toMatch(/<script [^>]*src="\/js\/index\.\w+\.js">/)
expect(index).not.toMatch(/<script [^>]*src="\/js\/foo\.\w+\.js">/)
expect(index).not.toMatch(/<script [^>]*src="\/js\/bar\.\w+\.js">/)
expect(index).toMatch(/<script [^>]*src="\/js\/index\.\w+\.js" type="module">/)
expect(index).not.toMatch(/<script [^>]*src="\/js\/foo\.\w+\.js" type="module">/)
expect(index).not.toMatch(/<script [^>]*src="\/js\/bar\.\w+\.js" type="module">/)

const foo = await project.read('dist/foo.html')
assertSharedAssets(foo)
Expand All @@ -143,9 +143,9 @@ test('build w/ multi page', async () => {
// expect(foo).not.toMatch(/<link [^>]*css\/chunk-\w+\.\w+\.css" rel="prefetch">/)
// expect(foo).not.toMatch(/<link [^>]*js\/chunk-\w+\.\w+\.js" rel="prefetch">/)
// should load correct page js
expect(foo).not.toMatch(/<script [^>]*src="\/js\/index\.\w+\.js">/)
expect(foo).toMatch(/<script [^>]*src="\/js\/foo\.\w+\.js">/)
expect(foo).not.toMatch(/<script [^>]*src="\/js\/bar\.\w+\.js">/)
expect(foo).not.toMatch(/<script [^>]*src="\/js\/index\.\w+\.js" type="module">/)
expect(foo).toMatch(/<script [^>]*src="\/js\/foo\.\w+\.js" type="module">/)
expect(foo).not.toMatch(/<script [^>]*src="\/js\/bar\.\w+\.js" type="module">/)

const bar = await project.read('dist/bar.html')
assertSharedAssets(bar)
Expand All @@ -162,9 +162,9 @@ test('build w/ multi page', async () => {
// expect(bar).toMatch(/<link [^>]*css\/chunk-\w+\.\w+\.css" rel="prefetch">/)
// expect(bar).toMatch(/<link [^>]*js\/chunk-\w+\.\w+\.js" rel="prefetch">/)
// should load correct page js
expect(bar).not.toMatch(/<script [^>]*src="\/js\/index\.\w+\.js">/)
expect(bar).not.toMatch(/<script [^>]*src="\/js\/foo\.\w+\.js">/)
expect(bar).toMatch(/<script [^>]*src="\/js\/bar\.\w+\.js">/)
expect(bar).not.toMatch(/<script [^>]*src="\/js\/index\.\w+\.js" type="module">/)
expect(bar).not.toMatch(/<script [^>]*src="\/js\/foo\.\w+\.js" type="module">/)
expect(bar).toMatch(/<script [^>]*src="\/js\/bar\.\w+\.js" type="module">/)

// assert pages work
const port = await portfinder.getPortPromise()
Expand Down
17 changes: 5 additions & 12 deletions packages/@vue/cli-service/lib/commands/build/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const defaults = {
clean: true,
target: 'app',
module: true,
formats: 'commonjs,umd,umd-min',
'unsafe-inline': true
}
Expand All @@ -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 <script type="module"> chunks for modern browsers`,
'--no-unsafe-inline': `build app without introducing inline scripts`,
'--target': `app | lib | wc | wc-async (default: ${defaults.target})`,
'--inline-vue': 'include the Vue module in the final bundle of library or web component target',
Expand All @@ -52,7 +53,7 @@ module.exports = (api, options) => {
}

process.env.VUE_CLI_BUILD_TARGET = args.target
if (args.modern && args.target === 'app') {
if (args.module && args.target === 'app') {
process.env.VUE_CLI_MODERN_MODE = true
if (!process.env.VUE_CLI_MODERN_BUILD) {
// main-process for legacy build
Expand All @@ -78,14 +79,6 @@ module.exports = (api, options) => {
}
delete process.env.VUE_CLI_MODERN_MODE
} else {
if (args.modern) {
const { warn } = require('@vue/cli-shared-utils')
warn(
`Modern mode only works with default target (app). ` +
`For libraries or web components, use the browserslist ` +
`config to specify target browsers.`
)
}
await build(args, api, options)
}
delete process.env.VUE_CLI_BUILD_TARGET
Expand All @@ -110,7 +103,7 @@ async function build (args, api, options) {
log()
const mode = api.service.mode
if (args.target === 'app') {
const bundleTag = args.modern
const bundleTag = args.module
? args.modernBuild
? `modern bundle `
: `legacy bundle `
Expand All @@ -132,7 +125,7 @@ async function build (args, api, options) {
}

const targetDir = api.resolve(options.outputDir)
const isLegacyBuild = args.target === 'app' && args.modern && !args.modernBuild
const isLegacyBuild = args.target === 'app' && args.module && !args.modernBuild

// resolve raw webpack config
let webpackConfig
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module.exports = (api, args, options) => {
})
}

if (args.modern) {
if (args.module) {
const ModernModePlugin = require('../../webpack/ModernModePlugin')
if (!args.modernBuild) {
// Inject plugin to extract build stats and write to disk
Expand Down
50 changes: 50 additions & 0 deletions packages/@vue/cli-service/lib/util/targets.js
Original file line number Diff line number Diff line change
@@ -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
}
Loading