mirror of
https://github.com/vuejs/vue-cli.git
synced 2026-01-27 01:28:56 -06:00
chore: merge dev
This commit is contained in:
@@ -42,7 +42,7 @@ beforeAll(async () => {
|
||||
test('dep from node_modules should not been transpiled', async () => {
|
||||
const { stdout } = await project.run('vue-cli-service build')
|
||||
|
||||
let $vendorjs = stdout.match(/(js\/vendors~app\.[^.]+\.js)/)[1]
|
||||
let $vendorjs = stdout.match(/(js\/chunk-vendors\.[^.]+\.js)/)[1]
|
||||
|
||||
$vendorjs = `dist/${$vendorjs}`
|
||||
$vendorjs = await project.read($vendorjs)
|
||||
@@ -58,7 +58,7 @@ test('dep from node_modules should been transpiled', async () => {
|
||||
|
||||
const { stdout } = await project.run('vue-cli-service build')
|
||||
|
||||
let $vendorjs = stdout.match(/(js\/vendors~app\.[^.]+\.js)/)[1]
|
||||
let $vendorjs = stdout.match(/(js\/chunk-vendors\.[^.]+\.js)/)[1]
|
||||
|
||||
$vendorjs = `dist/${$vendorjs}`
|
||||
$vendorjs = await project.read($vendorjs)
|
||||
|
||||
@@ -1,7 +1,27 @@
|
||||
module.exports = (api, { lintOnSave }) => {
|
||||
if (lintOnSave) {
|
||||
module.exports = (api, options) => {
|
||||
if (options.lintOnSave) {
|
||||
const extensions = require('./eslintOptions').extensions(api)
|
||||
const cacheIdentifier = genCacheIdentifier(api.resolve('.'))
|
||||
|
||||
// eslint-loader doesn't bust cache when eslint config changes
|
||||
// so we have to manually generate a cache identifier that takes the config
|
||||
// into account.
|
||||
const { genCacheConfig } = require('@vue/cli-shared-utils')
|
||||
const { cacheIdentifier } = genCacheConfig(
|
||||
api,
|
||||
options,
|
||||
[
|
||||
'eslint-loader',
|
||||
'eslint'
|
||||
],
|
||||
[
|
||||
'.eslintrc.js',
|
||||
'.eslintrc.yaml',
|
||||
'.eslintrc.yml',
|
||||
'.eslintrc.json',
|
||||
'.eslintrc',
|
||||
'package.json'
|
||||
]
|
||||
)
|
||||
|
||||
api.chainWebpack(webpackConfig => {
|
||||
webpackConfig.module
|
||||
@@ -18,7 +38,7 @@ module.exports = (api, { lintOnSave }) => {
|
||||
extensions,
|
||||
cache: true,
|
||||
cacheIdentifier,
|
||||
emitWarning: lintOnSave !== 'error',
|
||||
emitWarning: options.lintOnSave !== 'error',
|
||||
formatter: require('eslint/lib/formatters/codeframe')
|
||||
})
|
||||
})
|
||||
@@ -38,33 +58,3 @@ module.exports = (api, { lintOnSave }) => {
|
||||
require('./lint')(args, api)
|
||||
})
|
||||
}
|
||||
|
||||
// eslint-loader doesn't bust cache when eslint config changes
|
||||
// so we have to manually generate a cache identifier that takes the config
|
||||
// into account.
|
||||
function genCacheIdentifier (context) {
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const files = [
|
||||
'.eslintrc.js',
|
||||
'.eslintrc.yaml',
|
||||
'.eslintrc.yml',
|
||||
'.eslintrc.json',
|
||||
'.eslintrc',
|
||||
'package.json'
|
||||
]
|
||||
|
||||
const configTimeStamp = (() => {
|
||||
for (const file of files) {
|
||||
if (fs.existsSync(path.join(context, file))) {
|
||||
return fs.statSync(file).mtimeMs
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
return JSON.stringify({
|
||||
'eslint-loader': require('eslint-loader/package.json').version,
|
||||
'eslint': require('eslint/package.json').version,
|
||||
'config': configTimeStamp
|
||||
})
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ test('pwa', async () => {
|
||||
|
||||
// should split and preload app.js & vendor.js
|
||||
expect(index).toMatch(/<link [^>]+js\/app[^>]+\.js rel=preload>/)
|
||||
expect(index).toMatch(/<link [^>]+js\/vendors~app[^>]+\.js rel=preload>/)
|
||||
expect(index).toMatch(/<link [^>]+js\/chunk-vendors[^>]+\.js rel=preload>/)
|
||||
// should preload css
|
||||
expect(index).toMatch(/<link [^>]+app[^>]+\.css rel=preload>/)
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ module.exports = (api, options) => {
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
transpileOnly: true,
|
||||
appendTsSuffixTo: [/\.vue$/],
|
||||
appendTsSuffixTo: ['\\.vue$'],
|
||||
// https://github.com/TypeStrong/ts-loader#happypackmode-boolean-defaultfalse
|
||||
happyPackMode: useThreads
|
||||
}
|
||||
@@ -50,7 +50,7 @@ module.exports = (api, options) => {
|
||||
tsxRule.use('ts-loader').loader('ts-loader').tap(options => {
|
||||
options = Object.assign({}, options)
|
||||
delete options.appendTsSuffixTo
|
||||
options.appendTsxSuffixTo = [/\.vue$/]
|
||||
options.appendTsxSuffixTo = ['\\.vue$']
|
||||
return options
|
||||
})
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ test('build', async () => {
|
||||
const index = await project.read('dist/index.html')
|
||||
// should split and preload app.js & vendor.js
|
||||
expect(index).toMatch(/<link [^>]+js\/app[^>]+\.js rel=preload>/)
|
||||
expect(index).toMatch(/<link [^>]+js\/vendors~app[^>]+\.js rel=preload>/)
|
||||
expect(index).toMatch(/<link [^>]+js\/chunk-vendors[^>]+\.js rel=preload>/)
|
||||
// should preload css
|
||||
expect(index).toMatch(/<link [^>]+app[^>]+\.css rel=preload>/)
|
||||
|
||||
|
||||
@@ -22,12 +22,15 @@ const genConfig = (pkg = {}, env) => {
|
||||
return config
|
||||
}
|
||||
|
||||
const findRule = (config, lang, index = 2) => {
|
||||
const findRule = (config, lang, index = 3) => {
|
||||
const baseRule = config.module.rules.find(rule => {
|
||||
return rule.test.test(`.${lang}`)
|
||||
})
|
||||
// all CSS rules have oneOf with two child rules, one for <style lang="module">
|
||||
// and one for normal imports
|
||||
// all CSS rules have 4 oneOf rules:
|
||||
// - <style lang="module"> in Vue files
|
||||
// - <style> in Vue files
|
||||
// - *.modules.css imports from JS
|
||||
// - *.css imports from JS
|
||||
return baseRule.oneOf[index]
|
||||
}
|
||||
|
||||
@@ -76,7 +79,13 @@ test('production defaults', () => {
|
||||
})
|
||||
|
||||
test('CSS Modules rules', () => {
|
||||
const config = genConfig()
|
||||
const config = genConfig({
|
||||
vue: {
|
||||
css: {
|
||||
modules: true
|
||||
}
|
||||
}
|
||||
})
|
||||
LANGS.forEach(lang => {
|
||||
const expected = {
|
||||
importLoaders: lang === 'css' ? 1 : 2, // no postcss-loader
|
||||
@@ -85,10 +94,12 @@ test('CSS Modules rules', () => {
|
||||
sourceMap: false,
|
||||
modules: true
|
||||
}
|
||||
// module-query rules
|
||||
// vue-modules rules
|
||||
expect(findOptions(config, lang, 'css', 0)).toEqual(expected)
|
||||
// module-ext rules
|
||||
expect(findOptions(config, lang, 'css', 1)).toEqual(expected)
|
||||
// normal-modules rules
|
||||
expect(findOptions(config, lang, 'css', 2)).toEqual(expected)
|
||||
// normal rules
|
||||
expect(findOptions(config, lang, 'css', 3)).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
170
packages/@vue/cli-service/__tests__/multiPage.spec.js
Normal file
170
packages/@vue/cli-service/__tests__/multiPage.spec.js
Normal file
@@ -0,0 +1,170 @@
|
||||
jest.setTimeout(30000)
|
||||
|
||||
const path = require('path')
|
||||
const portfinder = require('portfinder')
|
||||
const { defaultPreset } = require('@vue/cli/lib/options')
|
||||
const { createServer } = require('http-server')
|
||||
const create = require('@vue/cli-test-utils/createTestProject')
|
||||
const serve = require('@vue/cli-test-utils/serveWithPuppeteer')
|
||||
const launchPuppeteer = require('@vue/cli-test-utils/launchPuppeteer')
|
||||
|
||||
async function makeProjectMultiPage (project) {
|
||||
await project.write('vue.config.js', `
|
||||
module.exports = {
|
||||
pages: {
|
||||
index: { entry: 'src/main.js' },
|
||||
foo: { entry: 'src/foo.js' },
|
||||
bar: { entry: 'src/bar.js' }
|
||||
},
|
||||
chainWebpack: config => {
|
||||
const splitOptions = config.optimization.get('splitChunks')
|
||||
config.optimization.splitChunks(Object.assign({}, splitOptions, {
|
||||
minSize: 10000
|
||||
}))
|
||||
}
|
||||
}
|
||||
`)
|
||||
await project.write('src/foo.js', `
|
||||
import Vue from 'vue'
|
||||
new Vue({
|
||||
el: '#app',
|
||||
render: h => h('h1', 'Foo')
|
||||
})
|
||||
`)
|
||||
await project.write('src/bar.js', `
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
new Vue({
|
||||
el: '#app',
|
||||
render: h => h(App)
|
||||
})
|
||||
`)
|
||||
const app = await project.read('src/App.vue')
|
||||
await project.write('src/App.vue', app.replace(
|
||||
`import HelloWorld from './components/HelloWorld.vue'`,
|
||||
`const HelloWorld = () => import('./components/HelloWorld.vue')`
|
||||
))
|
||||
}
|
||||
|
||||
test('serve w/ multi page', async () => {
|
||||
const project = await create('e2e-multi-page-serve', defaultPreset)
|
||||
|
||||
await makeProjectMultiPage(project)
|
||||
|
||||
await serve(
|
||||
() => project.run('vue-cli-service serve'),
|
||||
async ({ page, url, helpers }) => {
|
||||
expect(await helpers.getText('h1')).toMatch(`Welcome to Your Vue.js App`)
|
||||
|
||||
await page.goto(`${url}/foo.html`)
|
||||
expect(await helpers.getText('h1')).toMatch(`Foo`)
|
||||
|
||||
await page.goto(`${url}/bar.html`)
|
||||
expect(await helpers.getText('h1')).toMatch(`Welcome to Your Vue.js App`)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
let server, browser, page
|
||||
test('build w/ multi page', async () => {
|
||||
const project = await create('e2e-multi-page-build', defaultPreset)
|
||||
|
||||
await makeProjectMultiPage(project)
|
||||
|
||||
const { stdout } = await project.run('vue-cli-service build')
|
||||
expect(stdout).toMatch('Build complete.')
|
||||
|
||||
// should generate the HTML pages
|
||||
expect(project.has('dist/index.html')).toBe(true)
|
||||
expect(project.has('dist/foo.html')).toBe(true)
|
||||
expect(project.has('dist/bar.html')).toBe(true)
|
||||
|
||||
const assertSharedAssets = file => {
|
||||
// should split and preload vendor chunk
|
||||
expect(file).toMatch(/<link [^>]+js\/chunk-vendors[^>]+\.js rel=preload>/)
|
||||
// should split and preload common js and css
|
||||
expect(file).toMatch(/<link [^>]+js\/chunk-common[^>]+\.js rel=preload>/)
|
||||
expect(file).toMatch(/<link [^>]+chunk-common[^>]+\.css rel=preload>/)
|
||||
// should load common css
|
||||
expect(file).toMatch(/<link href=\/css\/chunk-common\.\w+\.css rel=stylesheet>/)
|
||||
// should load common js
|
||||
expect(file).toMatch(/<script [^>]+src=\/js\/chunk-vendors\.\w+\.js>/)
|
||||
expect(file).toMatch(/<script [^>]+src=\/js\/chunk-common\.\w+\.js>/)
|
||||
}
|
||||
|
||||
const index = await project.read('dist/index.html')
|
||||
assertSharedAssets(index)
|
||||
// should preload correct page file
|
||||
expect(index).toMatch(/<link [^>]+js\/index[^>]+\.js rel=preload>/)
|
||||
expect(index).not.toMatch(/<link [^>]+js\/foo[^>]+\.js rel=preload>/)
|
||||
expect(index).not.toMatch(/<link [^>]+js\/bar[^>]+\.js rel=preload>/)
|
||||
// should prefetch async chunk js and css
|
||||
expect(index).toMatch(/<link [^>]+css\/0\.\w+\.css rel=prefetch>/)
|
||||
expect(index).toMatch(/<link [^>]+js\/0\.\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>/)
|
||||
|
||||
const foo = await project.read('dist/foo.html')
|
||||
assertSharedAssets(foo)
|
||||
// should preload correct page file
|
||||
expect(foo).not.toMatch(/<link [^>]+js\/index[^>]+\.js rel=preload>/)
|
||||
expect(foo).toMatch(/<link [^>]+js\/foo[^>]+\.js rel=preload>/)
|
||||
expect(foo).not.toMatch(/<link [^>]+js\/bar[^>]+\.js rel=preload>/)
|
||||
// should not prefetch async chunk js and css because it's not used by
|
||||
// this entry
|
||||
expect(foo).not.toMatch(/<link [^>]+css\/0\.\w+\.css rel=prefetch>/)
|
||||
expect(foo).not.toMatch(/<link [^>]+js\/0\.\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>/)
|
||||
|
||||
const bar = await project.read('dist/bar.html')
|
||||
assertSharedAssets(bar)
|
||||
// should preload correct page file
|
||||
expect(bar).not.toMatch(/<link [^>]+js\/index[^>]+\.js rel=preload>/)
|
||||
expect(bar).not.toMatch(/<link [^>]+js\/foo[^>]+\.js rel=preload>/)
|
||||
expect(bar).toMatch(/<link [^>]+js\/bar[^>]+\.js rel=preload>/)
|
||||
// should prefetch async chunk js and css
|
||||
expect(bar).toMatch(/<link [^>]+css\/0\.\w+\.css rel=prefetch>/)
|
||||
expect(bar).toMatch(/<link [^>]+js\/0\.\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>/)
|
||||
|
||||
// assert pages work
|
||||
const port = await portfinder.getPortPromise()
|
||||
server = createServer({ root: path.join(project.dir, 'dist') })
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
server.listen(port, err => {
|
||||
if (err) return reject(err)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
|
||||
const url = `http://localhost:${port}/`
|
||||
const launched = await launchPuppeteer(url)
|
||||
browser = launched.browser
|
||||
page = launched.page
|
||||
|
||||
const getH1Text = async () => page.evaluate(() => {
|
||||
return document.querySelector('h1').textContent
|
||||
})
|
||||
|
||||
expect(await getH1Text()).toMatch('Welcome to Your Vue.js App')
|
||||
|
||||
await page.goto(`${url}foo.html`)
|
||||
expect(await getH1Text()).toMatch('Foo')
|
||||
|
||||
await page.goto(`${url}bar.html`)
|
||||
expect(await getH1Text()).toMatch('Welcome to Your Vue.js App')
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await browser.close()
|
||||
server.close()
|
||||
})
|
||||
@@ -1,5 +1,7 @@
|
||||
jest.setTimeout(45000)
|
||||
jest.setTimeout(60000)
|
||||
|
||||
const path = require('path')
|
||||
const fs = require('fs-extra')
|
||||
const { defaultPreset } = require('@vue/cli/lib/options')
|
||||
const create = require('@vue/cli-test-utils/createTestProject')
|
||||
const serve = require('@vue/cli-test-utils/serveWithPuppeteer')
|
||||
@@ -50,8 +52,6 @@ test('serve with router', async () => {
|
||||
test('serve with inline entry', async () => {
|
||||
const project = await create('e2e-serve-inline-entry', defaultPreset)
|
||||
|
||||
const path = require('path')
|
||||
const fs = require('fs-extra')
|
||||
await fs.move(
|
||||
path.resolve(project.dir, 'src/main.js'),
|
||||
path.resolve(project.dir, 'src/index.js')
|
||||
@@ -72,3 +72,24 @@ test('serve with inline entry', async () => {
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('serve with no public dir', async () => {
|
||||
const project = await create('e2e-serve-no-public', defaultPreset)
|
||||
|
||||
await fs.remove(path.resolve(project.dir, 'public'))
|
||||
|
||||
await serve(
|
||||
() => project.run('vue-cli-service serve'),
|
||||
async ({ nextUpdate, helpers }) => {
|
||||
const msg = `Welcome to Your Vue.js App`
|
||||
expect(await helpers.getText('h1')).toMatch(msg)
|
||||
|
||||
// test hot reload
|
||||
const file = await project.read(`src/App.vue`)
|
||||
project.write(`src/App.vue`, file.replace(msg, `Updated`))
|
||||
await nextUpdate() // wait for child stdout update signal
|
||||
await sleep(1000) // give the client time to update
|
||||
expect(await helpers.getText('h1')).toMatch(`Updated`)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -59,7 +59,7 @@ module.exports = (api, options) => {
|
||||
// respect inline build destination in copy plugin
|
||||
if (args.dest) {
|
||||
api.chainWebpack(config => {
|
||||
if (args.target === 'app') {
|
||||
if (config.plugins.has('copy')) {
|
||||
config.plugin('copy').tap(args => {
|
||||
args[0][0].to = targetDir
|
||||
return args
|
||||
|
||||
@@ -31,7 +31,7 @@ module.exports = (api, { entry, name }, options) => {
|
||||
const config = api.resolveChainableWebpackConfig()
|
||||
|
||||
// adjust css output name so they write to the same file
|
||||
if (options.css.extract !== false) {
|
||||
if (config.plugins.has('extract-css')) {
|
||||
config
|
||||
.plugin('extract-css')
|
||||
.tap(args => {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// config that are specific to --target app
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
module.exports = (api, options) => {
|
||||
api.chainWebpack(webpackConfig => {
|
||||
@@ -7,8 +9,11 @@ module.exports = (api, options) => {
|
||||
return
|
||||
}
|
||||
|
||||
const isProd = process.env.NODE_ENV === 'production'
|
||||
|
||||
// HTML plugin
|
||||
const resolveClientEnv = require('../util/resolveClientEnv')
|
||||
|
||||
const htmlOptions = {
|
||||
templateParameters: (compilation, assets, pluginOptions) => {
|
||||
// enhance html-webpack-plugin's built in template params
|
||||
@@ -27,62 +32,135 @@ module.exports = (api, options) => {
|
||||
}, resolveClientEnv(options.baseUrl, true /* raw */))
|
||||
}
|
||||
}
|
||||
// only set template path if index.html exists
|
||||
const htmlPath = api.resolve('public/index.html')
|
||||
if (require('fs').existsSync(htmlPath)) {
|
||||
htmlOptions.template = htmlPath
|
||||
|
||||
if (isProd) {
|
||||
Object.assign(htmlOptions, {
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true
|
||||
// more options:
|
||||
// https://github.com/kangax/html-minifier#options-quick-reference
|
||||
},
|
||||
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
|
||||
chunksSortMode: 'dependency'
|
||||
})
|
||||
}
|
||||
|
||||
webpackConfig
|
||||
.plugin('html')
|
||||
.use(require('html-webpack-plugin'), [htmlOptions])
|
||||
// resolve HTML file(s)
|
||||
const HTMLPlugin = require('html-webpack-plugin')
|
||||
const PreloadPlugin = require('@vue/preload-webpack-plugin')
|
||||
const multiPageConfig = options.pages
|
||||
const htmlPath = api.resolve('public/index.html')
|
||||
const defaultHtmlPath = path.resolve(__dirname, 'index-default.html')
|
||||
|
||||
// inject preload/prefetch to HTML
|
||||
const PreloadPlugin = require('preload-webpack-plugin')
|
||||
webpackConfig
|
||||
.plugin('preload')
|
||||
.use(PreloadPlugin, [{
|
||||
rel: 'preload',
|
||||
include: 'initial',
|
||||
fileBlacklist: [/\.map$/, /hot-update\.js$/]
|
||||
}])
|
||||
if (!multiPageConfig) {
|
||||
// default, single page setup.
|
||||
htmlOptions.template = fs.existsSync(htmlPath)
|
||||
? htmlPath
|
||||
: defaultHtmlPath
|
||||
|
||||
webpackConfig
|
||||
.plugin('prefetch')
|
||||
.use(PreloadPlugin, [{
|
||||
rel: 'prefetch',
|
||||
include: 'asyncChunks'
|
||||
}])
|
||||
|
||||
// copy static assets in public/
|
||||
webpackConfig
|
||||
.plugin('copy')
|
||||
.use(require('copy-webpack-plugin'), [[{
|
||||
from: api.resolve('public'),
|
||||
to: api.resolve(options.outputDir),
|
||||
ignore: ['index.html', '.DS_Store']
|
||||
}]])
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
// minify HTML
|
||||
webpackConfig
|
||||
.plugin('html')
|
||||
.tap(([options]) => [Object.assign(options, {
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true
|
||||
// more options:
|
||||
// https://github.com/kangax/html-minifier#options-quick-reference
|
||||
},
|
||||
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
|
||||
chunksSortMode: 'dependency'
|
||||
})])
|
||||
.use(HTMLPlugin, [htmlOptions])
|
||||
|
||||
// code splitting
|
||||
// inject preload/prefetch to HTML
|
||||
webpackConfig
|
||||
.plugin('preload')
|
||||
.use(PreloadPlugin, [{
|
||||
rel: 'preload',
|
||||
include: 'initial',
|
||||
fileBlacklist: [/\.map$/, /hot-update\.js$/]
|
||||
}])
|
||||
|
||||
webpackConfig
|
||||
.plugin('prefetch')
|
||||
.use(PreloadPlugin, [{
|
||||
rel: 'prefetch',
|
||||
include: 'asyncChunks'
|
||||
}])
|
||||
} else {
|
||||
// multi-page setup
|
||||
webpackConfig.entryPoints.clear()
|
||||
|
||||
const pages = Object.keys(multiPageConfig)
|
||||
|
||||
pages.forEach(name => {
|
||||
const {
|
||||
entry,
|
||||
template = `public/${name}.html`,
|
||||
filename = `${name}.html`
|
||||
} = multiPageConfig[name]
|
||||
// inject entry
|
||||
webpackConfig.entry(name).add(api.resolve(entry))
|
||||
|
||||
// inject html plugin for the page
|
||||
const pageHtmlOptions = Object.assign({}, htmlOptions, {
|
||||
chunks: ['chunk-vendors', 'chunk-common', name],
|
||||
template: fs.existsSync(template) ? template : defaultHtmlPath,
|
||||
filename
|
||||
})
|
||||
|
||||
webpackConfig
|
||||
.plugin(`html-${name}`)
|
||||
.use(HTMLPlugin, [pageHtmlOptions])
|
||||
})
|
||||
|
||||
pages.forEach(name => {
|
||||
const { filename = `${name}.html` } = multiPageConfig[name]
|
||||
webpackConfig
|
||||
.plugin(`preload-${name}`)
|
||||
.use(PreloadPlugin, [{
|
||||
rel: 'preload',
|
||||
includeHtmlNames: [filename],
|
||||
include: {
|
||||
type: 'initial',
|
||||
entries: [name]
|
||||
},
|
||||
fileBlacklist: [/\.map$/, /hot-update\.js$/]
|
||||
}])
|
||||
|
||||
webpackConfig
|
||||
.plugin(`prefetch-${name}`)
|
||||
.use(PreloadPlugin, [{
|
||||
rel: 'prefetch',
|
||||
includeHtmlNames: [filename],
|
||||
include: {
|
||||
type: 'asyncChunks',
|
||||
entries: [name]
|
||||
}
|
||||
}])
|
||||
})
|
||||
}
|
||||
|
||||
// copy static assets in public/
|
||||
if (fs.existsSync(api.resolve('public'))) {
|
||||
webpackConfig
|
||||
.plugin('copy')
|
||||
.use(require('copy-webpack-plugin'), [[{
|
||||
from: api.resolve('public'),
|
||||
to: api.resolve(options.outputDir),
|
||||
ignore: ['index.html', '.DS_Store']
|
||||
}]])
|
||||
}
|
||||
|
||||
// code splitting
|
||||
if (isProd) {
|
||||
webpackConfig
|
||||
.optimization.splitChunks({
|
||||
chunks: 'all'
|
||||
chunks: 'all',
|
||||
name: (m, chunks, cacheGroup) => `chunk-${cacheGroup}`,
|
||||
cacheGroups: {
|
||||
vendors: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
priority: -10
|
||||
},
|
||||
common: {
|
||||
minChunks: 2,
|
||||
priority: -20,
|
||||
reuseExistingChunk: true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -26,7 +26,12 @@ module.exports = (api, options) => {
|
||||
.end()
|
||||
.alias
|
||||
.set('@', api.resolve('src'))
|
||||
.set('vue$', options.compiler ? 'vue/dist/vue.esm.js' : 'vue/dist/vue.runtime.esm.js')
|
||||
.set(
|
||||
'vue$',
|
||||
options.runtimeCompiler
|
||||
? 'vue/dist/vue.esm.js'
|
||||
: 'vue/dist/vue.runtime.esm.js'
|
||||
)
|
||||
|
||||
webpackConfig.resolveLoader
|
||||
.set('symlinks', true)
|
||||
|
||||
@@ -14,6 +14,7 @@ module.exports = (api, options) => {
|
||||
const getAssetPath = require('../util/getAssetPath')
|
||||
|
||||
const {
|
||||
modules = false,
|
||||
extract = true,
|
||||
sourceMap = false,
|
||||
localIdentName = '[name]_[local]_[hash:base64:5]',
|
||||
@@ -25,7 +26,7 @@ module.exports = (api, options) => {
|
||||
const shouldExtract = isProd && extract !== false && !shadowMode
|
||||
const extractOptions = Object.assign({
|
||||
filename: getAssetPath(options, `css/[name].[contenthash:8].css`),
|
||||
chunkFilename: getAssetPath(options, 'css/[name].[id].[contenthash:8].css')
|
||||
chunkFilename: getAssetPath(options, 'css/[name].[contenthash:8].css')
|
||||
}, extract && typeof extract === 'object' ? extract : {})
|
||||
|
||||
// check if the project has a valid postcss config
|
||||
@@ -43,15 +44,20 @@ module.exports = (api, options) => {
|
||||
const baseRule = webpackConfig.module.rule(lang).test(test)
|
||||
|
||||
// rules for <style lang="module">
|
||||
const modulesRule = baseRule.oneOf('modules-query').resourceQuery(/module/)
|
||||
applyLoaders(modulesRule, true)
|
||||
const vueModulesRule = baseRule.oneOf('vue-modules').resourceQuery(/module/)
|
||||
applyLoaders(vueModulesRule, true)
|
||||
|
||||
// rules for <style>
|
||||
const vueNormalRule = baseRule.oneOf('vue').resourceQuery(/\?vue/)
|
||||
applyLoaders(vueNormalRule, false)
|
||||
|
||||
// rules for *.module.* files
|
||||
const modulesExtRule = baseRule.oneOf('modules-ext').test(/\.module\.\w+$/)
|
||||
applyLoaders(modulesExtRule, true)
|
||||
const extModulesRule = baseRule.oneOf('normal-modules').test(/\.module\.\w+$/)
|
||||
applyLoaders(extModulesRule, true)
|
||||
|
||||
// rules for normal CSS imports
|
||||
const normalRule = baseRule.oneOf('normal')
|
||||
applyLoaders(normalRule, false)
|
||||
applyLoaders(normalRule, modules)
|
||||
|
||||
function applyLoaders (rule, modules) {
|
||||
if (shouldExtract) {
|
||||
|
||||
12
packages/@vue/cli-service/lib/config/index-default.html
Normal file
12
packages/@vue/cli-service/lib/config/index-default.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Vue App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,14 +5,16 @@ const schema = createSchema(joi => joi.object({
|
||||
devBaseUrl: joi.string(),
|
||||
outputDir: joi.string(),
|
||||
assetsDir: joi.string(),
|
||||
compiler: joi.boolean(),
|
||||
runtimeCompiler: joi.boolean(),
|
||||
transpileDependencies: joi.array(),
|
||||
productionSourceMap: joi.boolean(),
|
||||
parallel: joi.boolean(),
|
||||
devServer: joi.object(),
|
||||
pages: joi.object(),
|
||||
|
||||
// css
|
||||
css: joi.object({
|
||||
modules: joi.boolean(),
|
||||
localIdentName: joi.string(),
|
||||
extract: joi.alternatives().try(joi.boolean(), joi.object()),
|
||||
sourceMap: joi.boolean(),
|
||||
@@ -65,8 +67,12 @@ exports.defaults = () => ({
|
||||
// enabled by default if the machine has more than 1 cores
|
||||
parallel: require('os').cpus().length > 1,
|
||||
|
||||
// multi-page config
|
||||
pages: undefined,
|
||||
|
||||
css: {
|
||||
// extract: true,
|
||||
// modules: false,
|
||||
// localIdentName: '[name]_[local]_[hash:base64:5]',
|
||||
// sourceMap: false,
|
||||
// loaderOptions: {}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"dependencies": {
|
||||
"@vue/cli-overlay": "^3.0.0-beta.11",
|
||||
"@vue/cli-shared-utils": "^3.0.0-beta.11",
|
||||
"@vue/preload-webpack-plugin": "^1.0.0",
|
||||
"@vue/web-component-wrapper": "^1.2.0",
|
||||
"acorn": "^5.5.3",
|
||||
"address": "^1.0.3",
|
||||
@@ -50,7 +51,6 @@
|
||||
"ora": "^2.1.0",
|
||||
"portfinder": "^1.0.13",
|
||||
"postcss-loader": "^2.1.5",
|
||||
"preload-webpack-plugin": "^3.0.0-alpha.1",
|
||||
"read-pkg": "^3.0.0",
|
||||
"semver": "^5.5.0",
|
||||
"slash": "^2.0.0",
|
||||
|
||||
@@ -1,25 +1,44 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const hash = require('hash-sum')
|
||||
|
||||
exports.genCacheConfig = (api, options, id, configFile) => {
|
||||
const cacheDirectory = process.env.VUE_CLI_TEST
|
||||
? path.resolve(__dirname, `../../../../node_modules/.cache/${id}`)
|
||||
: api.resolve(`node_modules/.cache/${id}`)
|
||||
exports.genCacheConfig = (api, options, deps, configFiles) => {
|
||||
if (!Array.isArray(deps)) {
|
||||
deps = [deps]
|
||||
}
|
||||
const id = deps[0]
|
||||
const cacheDirectory = api.resolve(`node_modules/.cache/${id}`)
|
||||
|
||||
const variables = {
|
||||
[id]: require(`${id}/package.json`).version,
|
||||
'cache-loader': require('cache-loader/package.json').version,
|
||||
env: process.env.NODE_ENV,
|
||||
test: !!process.env.VUE_CLI_TEST,
|
||||
config: (options.chainWebpack || '').toString() + (options.configureWebpack || '').toString()
|
||||
config: [options.chainWebpack, options.configureWebpack]
|
||||
}
|
||||
if (configFile) {
|
||||
const file = api.resolve(configFile)
|
||||
if (fs.existsSync(file)) {
|
||||
variables.configFile = fs.readFileSync(configFile, 'utf-8')
|
||||
|
||||
for (const dep of deps) {
|
||||
variables[dep] = require(`${dep}/package.json`).version
|
||||
}
|
||||
|
||||
const readConfig = file => {
|
||||
const absolutePath = api.resolve(file)
|
||||
if (fs.existsSync(absolutePath)) {
|
||||
return fs.readFileSync(absolutePath, 'utf-8')
|
||||
}
|
||||
}
|
||||
const cacheIdentifier = JSON.stringify(variables)
|
||||
|
||||
if (configFiles) {
|
||||
if (!Array.isArray(configFiles)) {
|
||||
configFiles = [configFiles]
|
||||
}
|
||||
for (const file of configFiles) {
|
||||
const content = readConfig(file)
|
||||
if (content) {
|
||||
variables.configFiles = content
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cacheIdentifier = hash(variables)
|
||||
return { cacheDirectory, cacheIdentifier }
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"chalk": "^2.3.0",
|
||||
"cmd-shim": "^2.0.2",
|
||||
"execa": "^0.10.0",
|
||||
"hash-sum": "^1.0.2",
|
||||
"joi": "^12.0.0",
|
||||
"node-ipc": "^9.1.1",
|
||||
"opn": "^5.2.0",
|
||||
|
||||
Reference in New Issue
Block a user