8000 feat: support `vue.config.mjs` by haoqunjiang · Pull Request #6405 · vuejs/vue-cli · GitHub
[go: up one dir, main page]

Skip to content

feat: support vue.config.mjs #6405

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 6 commits into from
Apr 13, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension 8000


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: support vue.config.mjs
In Node.js 12.17+ only.
Otherwise, the `--experimental-modules` flag need to be turned on for
Node.

Not compatible with `eslint-import-resolver-webpack`.
  • Loading branch information
haoqunjiang committed Apr 7, 2021
commit d8877b51f73da3ee177b09eece02539b8b345ddc
75 changes: 43 additions & 32 deletions packages/@vue/cli-service/__tests__/ServiceESM.spec.js
8000
Original file line number Diff line number Diff line change
@@ -1,52 +1,63 @@
const Service = require('../lib/Service')

const path = require('path')
const configPath = path.resolve('/', 'vue.config.cjs')

jest.mock('fs')
const fs = require('fs')

beforeEach(() => {
fs.writeFileSync(path.resolve('/', 'package.json'), JSON.stringify({
type: 'module',
vue: {
lintOnSave: 'default'
}
}, null, 2))
})

afterEach(() => {
if (fs.existsSync(configPath)) {
fs.unlinkSync(configPath)
}
const fs = require('fs-extra')

const { defaultPreset } = require('@vue/cli/lib/options')
const create = require('@vue/cli-test-utils/createTestProject')
const { loadModule } = require('@vue/cli-shared-utils')

let project
beforeAll(async () => {
project = await create('service-esm-test', defaultPreset)
const pkg = JSON.parse(await project.read('package.json'))
pkg.type = 'module'
pkg.vue = { lintOnSave: 'default' }
await project.write('package.json', JSON.stringify(pkg, null, 2))
fs.renameSync(path.resolve(project.dir, 'babel.config.js'), path.resolve(project.dir, 'babel.config.cjs'))
})

const createService = () => {
const service = new Service('/', {
const createService = async () => {
const Service = loadModule('@vue/cli-service/lib/Service', project.dir)
const service = new Service(project.dir, {
plugins: [],
useBuiltIn: false
})
service.init()
await service.init()
return service
}

// vue.config.cjs has higher priority

test('load project options from package.json', async () => {
const service = createService()
const service = await createService()
expect(service.projectOptions.lintOnSave).toBe('default')
})

test('load project options from vue.config.cjs', async () => {
fs.writeFileSync(configPath, '')
jest.mock(configPath, () => ({ lintOnSave: true }), { virtual: true })
const service = createService()
const configPath = path.resolve(project.dir, './vue.config.cjs')
fs.writeFileSync(configPath, 'module.exports = { lintOnSave: true }')
const service = await createService()
expect(service.projectOptions.lintOnSave).toBe(true)
await fs.unlinkSync(configPath)
})

test('load project options from vue.config.cjs as a function', async () => {
fs.writeFileSync(configPath, '')
jest.mock(configPath, () => function () { return { lintOnSave: true } }, { virtual: true })
const service = createService()
const configPath = path.resolve(project.dir, './vue.config.cjs')
fs.writeFileSync(configPath, 'module.exports = function () { return { lintOnSave: true } }')
const service = await createService()
expect(service.projectOptions.lintOnSave).toBe(true)
await fs.unlinkSync(configPath)
})

test('load project options from vue.config.js', async () => {
const configPath = path.resolve(project.dir, './vue.config.js')
fs.writeFileSync(configPath, 'export default { lintOnSave: true }')
const service = await createService()
expect(service.projectOptions.lintOnSave).toBe(true)
await fs.unlinkSync(configPath)
})

test('load project options from vue.config.mjs', async () => {
const configPath = path.resolve(project.dir, './vue.config.mjs')
fs.writeFileSync(configPath, 'export default { lintOnSave: true }')
const service = await createService()
expect(service.projectOptions.lintOnSave).toBe(true)
await fs.unlinkSync(configPath)
})
137 changes: 30 additions & 107 deletions packages/@vue/cli-service/lib/Service.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const fs = require('fs')
const path = require('path')
const debug = require('debug')
const { merge } = require('webpack-merge')
Expand All @@ -7,10 +6,12 @@ const PluginAPI = require('./PluginAPI')
const dotenv = require('dotenv')
const dotenvExpand = require('dotenv-expand')
const defaultsDeep = require('lodash.defaultsdeep')
const { chalk, warn, error, isPlugin, resolvePluginId, loadModule, resolvePkg, resolveModule } = require('@vue/cli-shared-utils')
const { warn, error, isPlugin, resolvePluginId, loadModule, resolvePkg, resolveModule } = require('@vue/cli-shared-utils')

const { defaults, validate } = require('./options')
const checkWebpack = require('@vue/cli-service/lib/util/checkWebpack')
const { defaults } = require('./options')
const checkWebpack = require('./util/checkWebpack')
const loadFileConfig = require('./util/loadFileConfig')
const resolveUserConfig = require('./util/resolveUserConfig')

module.exports = class Service {
constructor (context, { plugins, pkg, inlineOptions, useBuiltIn } = {}) {
Expand Down Expand Up @@ -55,7 +56,7 @@ module.exports = class Service {
return pkg
}

init (mode = process.env.VUE_CLI_MODE) {
async init (mode = process.env.VUE_CLI_MODE) {
if (this.initialized) {
return
}
Expand All @@ -70,7 +71,7 @@ module.exports = class Service {
this.loadEnv()

// load user config
const userOptions = this.loadUserOptions()
const userOptions = await this.loadUserOptions()
this.projectOptions = defaultsDeep(userOptions, defaults())

debug('vue:project-config')(this.projectOptions)
Expand Down Expand Up @@ -227,7 +228,7 @@ module.exports = class Service {
this.setPluginsToSkip(args)

// load env variables, load user config, apply plugins
this.init(mode)
await this.init(mode)

args._ = args._ || []
let command = this.commands[name]
Expand Down Expand Up @@ -316,110 +317,32 @@ module.exports = class Service {
return config
}

// Note: we intentionally make this function synchronous by default
// because eslint-import-resolver-webpack does not support async webpack configs.
loadUserOptions () {
// vue.config.c?js
let fileConfig, pkgConfig, resolved, resolvedFrom
const esm = this.pkg.type && this.pkg.type === 'module'

const possibleConfigPaths = [
process.env.VUE_CLI_SERVICE_CONFIG_PATH,
'./vue.config.js',
'./vue.config.cjs'
]

let fileConfigPath
for (const p of possibleConfigPaths) {
const resolvedPath = p && path.resolve(this.context, p)
if (resolvedPath && fs.existsSync(resolvedPath)) {
fileConfigPath = resolvedPath
break
}
}

if (fileConfigPath) {
if (esm && fileConfigPath === './vue.config.js') {
throw new Error(`Please rename ${chalk.bold('vue.config.js')} to ${chalk.bold('vue.config.cjs')} when ECMAScript modules is enabled`)
}

try {
fileConfig = loadModule(fileConfigPath, this.context)

if (typeof fileConfig === 'function') {
fileConfig = fileConfig()
}

if (!fileConfig || typeof fileConfig !== 'object') {
// TODO: show throw an Error here, to be fixed in v5
error(
`Error loading ${chalk.bold(fileConfigPath)}: should export an object or a function that returns object.`
)
fileConfig = null
}
} catch (e) {
error(`Error loading ${chalk.bold(fileConfigPath)}:`)
throw e
}
}

// package.vue
pkgConfig = this.pkg.vue
if (pkgConfig && typeof pkgConfig !== 'object') {
error(
`Error loading vue-cli config in ${chalk.bold(`package.json`)}: ` +
`the "vue" field should be an object.`
)
pkgConfig = null
}

if (fileConfig) {
if (pkgConfig) {
warn(
`"vue" field in package.json ignored ` +
`due to presence of ${chalk.bold('vue.config.js')}.`
)
warn(
`You should migrate it into ${chalk.bold('vue.config.js')} ` +
`and remove it from package.json.`
)
}
resolved = fileConfig
resolvedFrom = 'vue.config.js'
} else if (pkgConfig) {
resolved = pkgConfig
resolvedFrom = '"vue" field in package.json'
} else {
resolved = this.inlineOptions || {}
resolvedFrom = 'inline options'
}

// normalize some options
ensureSlash(resolved, 'publicPath')
if (typeof resolved.publicPath === 'string') {
resolved.publicPath = resolved.publicPath.replace(/^\.\//, '')
const { fileConfig, fileConfigPath } = loadFileConfig(this.context)

// Seems we can't use `instanceof Promise` here (would fail the tests)
if (fileConfig && typeof fileConfig.then === 'function') {
return fileConfig
.then(mod => {
// fs.writeFileSync(`${this.context}/aaaa`, `mod ${JSON.stringify(mod, null, 2)}`)
return mod.default
})
.then(loadedConfig => resolveUserConfig({
inlineOptions: this.inlineOptions,
pkgConfig: this.pkg.vue,
fileConfig: loadedConfig,
fileConfigPath
}))
}
removeSlash(resolved, 'outputDir')

// validate options
validate(resolved, msg => {
error(
`Invalid options in ${chalk.bold(resolvedFrom)}: ${msg}`
)
return resolveUserConfig({
inlineOptions: this.inlineOptions,
pkgConfig: this.pkg.vue,
fileConfig,
fileConfigPath
})

return resolved
}
}

function ensureSlash (config, key) {
const val = config[key]
if (typeof val === 'string') {
config[key] = val.replace(/([^/])$/, '$1/')
}
}

function removeSlash (config, key) {
if (typeof config[key] === 'string') {
config[key] = config[key].replace(/\/$/g, '')
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/@vue/cli-service/lib/util/checkWebpack.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ module.exports = function checkWebpack (cwd) {
// Check the package.json,
// and only load from the project if webpack is explictly depended on,
// in case of accidental hoisting.

let pkg = {}
try {
pkg = loadModule('./package.json', cwd)
Expand Down
38 changes: 38 additions & 0 deletions packages/@vue/cli-service/lib/util/loadFileConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const fs = require('fs')
const path = require('path')

const isFileEsm = require('is-file-esm')
const { loadModule } = require('@vue/cli-shared-utils')

module.exports = function loadFileConfig (context) {
let fileConfig, fileConfigPath

const possibleConfigPaths = [
process.env.VUE_CLI_SERVICE_CONFIG_PATH,
'./vue.config.js',
'./vue.config.cjs',
'./vue.config.mjs'
]
for (const p of possibleConfigPaths) {
const resolvedPath = p && path.resolve(context, p)
if (resolvedPath && fs.existsSync(resolvedPath)) {
fileConfigPath = resolvedPath
break
}
}

if (fileConfigPath) {
const { esm } = isFileEsm.sync(fileConfigPath)

if (esm) {
fileConfig = import(fileConfigPath)
} else {
fileConfig = loadModule(fileConfigPath, context)
}
}

return {
fileConfig,
fileConfigPath
}
}
Loading
0