Merge remote-tracking branch 'upstream/main'

This commit is contained in:
2026-08-26 09:31:34 +08:00
parent 9b8c4bfab6
commit 394eb0285d
34137 changed files with 3589424 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017-present, Yuxi (Evan) You
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+5
View File
@@ -0,0 +1,5 @@
# @vue/cli-service
> service for vue-cli
[Full Docs](https://cli.vuejs.org/)
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env node
const { semver, error } = require('@vue/cli-shared-utils')
const requiredVersion = require('../package.json').engines.node
if (!semver.satisfies(process.version, requiredVersion)) {
error(
`You are using Node ${process.version}, but vue-cli-service ` +
`requires Node ${requiredVersion}.\nPlease upgrade your Node version.`
)
process.exit(1)
}
const Service = require('../lib/Service')
const service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd())
const rawArgv = process.argv.slice(2)
const args = require('minimist')(rawArgv, {
boolean: [
// build
'modern',
'report',
'report-json',
'inline-vue',
'watch',
// serve
'open',
'copy',
'https',
// inspect
'verbose'
]
})
const command = args._[0]
service.run(command, args, rawArgv).catch(err => {
error(err)
process.exit(1)
})
+67
View File
@@ -0,0 +1,67 @@
module.exports = (api, options) => {
api.render('./template', {
doesCompile: api.hasPlugin('babel') || api.hasPlugin('typescript')
})
api.extendPackage({
scripts: {
'serve': 'vue-cli-service serve',
'build': 'vue-cli-service build'
},
dependencies: {
'vue': '^2.6.11'
},
devDependencies: {
'vue-template-compiler': '^2.6.11'
},
browserslist: [
'> 1%',
'last 2 versions',
'not dead'
]
})
if (options.cssPreprocessor) {
const deps = {
sass: {
sass: '^1.26.5',
'sass-loader': '^8.0.2'
},
'node-sass': {
'node-sass': '^4.12.0',
'sass-loader': '^8.0.2'
},
'dart-sass': {
sass: '^1.26.5',
'sass-loader': '^8.0.2'
},
less: {
'less': '^3.0.4',
'less-loader': '^5.0.0'
},
stylus: {
'stylus': '^0.54.7',
'stylus-loader': '^3.0.2'
}
}
api.extendPackage({
devDependencies: deps[options.cssPreprocessor]
})
}
// for v3 compatibility
if (options.router && !api.hasPlugin('router')) {
require('./router')(api, options)
}
// for v3 compatibility
if (options.vuex && !api.hasPlugin('vuex')) {
require('./vuex')(api)
}
// additional tooling configurations
if (options.configs) {
api.extendPackage(options.configs)
}
}
+5
View File
@@ -0,0 +1,5 @@
module.exports = (api, options) => {
require('@vue/cli-plugin-router/generator')(api, {
historyMode: options.routerHistoryMode
})
}
+34
View File
@@ -0,0 +1,34 @@
.DS_Store
node_modules
/dist
<%_ if (rootOptions.plugins && rootOptions.plugins['@vue/cli-plugin-e2e-nightwatch']) { _%>
/tests/e2e/reports/
selenium-debug.log
chromedriver.log
geckodriver.log
<%_ } _%>
<%_ if (rootOptions.plugins && rootOptions.plugins['@vue/cli-plugin-e2e-cypress']) { _%>
/tests/e2e/videos/
/tests/e2e/screenshots/
<%_ } _%>
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<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">
<link rel="icon" href="<%%= BASE_URL %%>favicon.ico">
<title><%%= htmlWebpackPlugin.options.title %%></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%%= htmlWebpackPlugin.options.title %%> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
+54
View File
@@ -0,0 +1,54 @@
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<%_ if (!rootOptions.bare) { _%>
<HelloWorld msg="Welcome to Your Vue.js App"/>
<%_ } else { _%>
<h1>Welcome to Your Vue.js App</h1>
<%_ } _%>
</div>
</template>
<%_ if (!rootOptions.bare) { _%>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
<%_ if (rootOptions.cssPreprocessor !== 'stylus') { _%>
<style<%-
rootOptions.cssPreprocessor
? ` lang="${
rootOptions.cssPreprocessor.includes('sass')
? 'scss'
: rootOptions.cssPreprocessor
}"`
: ``
%>>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
<%_ } else { _%>
<style lang="stylus">
#app
font-family Avenir, Helvetica, Arial, sans-serif
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
text-align center
color #2c3e50
margin-top 60px
</style>
<%_ } _%>
<%_ } _%>
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

@@ -0,0 +1,87 @@
<%_ if (!rootOptions.bare) { _%>
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<%_ for (plugin of plugins) { _%>
<li><a href="<%- plugin.link %>" target="_blank" rel="noopener"><%- plugin.name %></a></li>
<%_ } _%>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<%_ if (rootOptions.cssPreprocessor !== 'stylus') { _%>
<style scoped<%-
rootOptions.cssPreprocessor
? ` lang="${
rootOptions.cssPreprocessor.includes('sass')
? 'scss'
: rootOptions.cssPreprocessor
}"`
: ``
%>>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
<%_ } else { _%>
<style scoped lang="stylus">
h3
margin 40px 0 0
ul
list-style-type none
padding 0
li
display inline-block
margin 0 10px
a
color #42b983
</style>
<%_ } _%>
<%_ } _%>
+12
View File
@@ -0,0 +1,12 @@
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
<%_ if (doesCompile) { _%>
render: h => h(App),
<%_ } else { _%>
render: function (h) { return h(App) },
<%_ } _%>
}).$mount('#app')
+3
View File
@@ -0,0 +1,3 @@
module.exports = api => {
require('@vue/cli-plugin-vuex/generator')(api)
}
+212
View File
@@ -0,0 +1,212 @@
const path = require('path')
const hash = require('hash-sum')
const { semver, matchesPluginId } = require('@vue/cli-shared-utils')
// Note: if a plugin-registered command needs to run in a specific default mode,
// the plugin needs to expose it via `module.exports.defaultModes` in the form
// of { [commandName]: mode }. This is because the command mode needs to be
// known and applied before loading user options / applying plugins.
class PluginAPI {
/**
* @param {string} id - Id of the plugin.
* @param {Service} service - A vue-cli-service instance.
*/
constructor (id, service) {
this.id = id
this.service = service
}
get version () {
return require('../package.json').version
}
assertVersion (range) {
if (typeof range === 'number') {
if (!Number.isInteger(range)) {
throw new Error('Expected string or integer value.')
}
range = `^${range}.0.0-0`
}
if (typeof range !== 'string') {
throw new Error('Expected string or integer value.')
}
if (semver.satisfies(this.version, range, { includePrerelease: true })) return
throw new Error(
`Require @vue/cli-service "${range}", but was loaded with "${this.version}".`
)
}
/**
* Current working directory.
*/
getCwd () {
return this.service.context
}
/**
* Resolve path for a project.
*
* @param {string} _path - Relative path from project root
* @return {string} The resolved absolute path.
*/
resolve (_path) {
return path.resolve(this.service.context, _path)
}
/**
* Check if the project has a given plugin.
*
* @param {string} id - Plugin id, can omit the (@vue/|vue-|@scope/vue)-cli-plugin- prefix
* @return {boolean}
*/
hasPlugin (id) {
return this.service.plugins.some(p => matchesPluginId(id, p.id))
}
/**
* Register a command that will become available as `vue-cli-service [name]`.
*
* @param {string} name
* @param {object} [opts]
* {
* description: string,
* usage: string,
* options: { [string]: string }
* }
* @param {function} fn
* (args: { [string]: string }, rawArgs: string[]) => ?Promise
*/
registerCommand (name, opts, fn) {
if (typeof opts === 'function') {
fn = opts
opts = null
}
this.service.commands[name] = { fn, opts: opts || {}}
}
/**
* Register a function that will receive a chainable webpack config
* the function is lazy and won't be called until `resolveWebpackConfig` is
* called
*
* @param {function} fn
*/
chainWebpack (fn) {
this.service.webpackChainFns.push(fn)
}
/**
* Register
* - a webpack configuration object that will be merged into the config
* OR
* - a function that will receive the raw webpack config.
* the function can either mutate the config directly or return an object
* that will be merged into the config.
*
* @param {object | function} fn
*/
configureWebpack (fn) {
this.service.webpackRawConfigFns.push(fn)
}
/**
* Register a dev serve config function. It will receive the express `app`
* instance of the dev server.
*
* @param {function} fn
*/
configureDevServer (fn) {
this.service.devServerConfigFns.push(fn)
}
/**
* Resolve the final raw webpack config, that will be passed to webpack.
*
* @param {ChainableWebpackConfig} [chainableConfig]
* @return {object} Raw webpack config.
*/
resolveWebpackConfig (chainableConfig) {
return this.service.resolveWebpackConfig(chainableConfig)
}
/**
* Resolve an intermediate chainable webpack config instance, which can be
* further tweaked before generating the final raw webpack config.
* You can call this multiple times to generate different branches of the
* base webpack config.
* See https://github.com/mozilla-neutrino/webpack-chain
*
* @return {ChainableWebpackConfig}
*/
resolveChainableWebpackConfig () {
return this.service.resolveChainableWebpackConfig()
}
/**
* Generate a cache identifier from a number of variables
*/
genCacheConfig (id, partialIdentifier, configFiles = []) {
const fs = require('fs')
const cacheDirectory = this.resolve(`node_modules/.cache/${id}`)
// replace \r\n to \n generate consistent hash
const fmtFunc = conf => {
if (typeof conf === 'function') {
return conf.toString().replace(/\r\n?/g, '\n')
}
return conf
}
const variables = {
partialIdentifier,
'cli-service': require('../package.json').version,
'cache-loader': require('cache-loader/package.json').version,
env: process.env.NODE_ENV,
test: !!process.env.VUE_CLI_TEST,
config: [
fmtFunc(this.service.projectOptions.chainWebpack),
fmtFunc(this.service.projectOptions.configureWebpack)
]
}
if (!Array.isArray(configFiles)) {
configFiles = [configFiles]
}
configFiles = configFiles.concat([
'package-lock.json',
'yarn.lock',
'pnpm-lock.yaml'
])
const readConfig = file => {
const absolutePath = this.resolve(file)
if (!fs.existsSync(absolutePath)) {
return
}
if (absolutePath.endsWith('.js')) {
// should evaluate config scripts to reflect environment variable changes
try {
return JSON.stringify(require(absolutePath))
} catch (e) {
return fs.readFileSync(absolutePath, 'utf-8')
}
} else {
return fs.readFileSync(absolutePath, 'utf-8')
}
}
variables.configFiles = configFiles.map(file => {
const content = readConfig(file)
return content && content.replace(/\r\n?/g, '\n')
})
const cacheIdentifier = hash(variables)
return { cacheDirectory, cacheIdentifier }
}
}
module.exports = PluginAPI
+438
View File
@@ -0,0 +1,438 @@
const fs = require('fs')
const path = require('path')
const debug = require('debug')
const merge = require('webpack-merge')
const Config = require('webpack-chain')
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 } = require('@vue/cli-shared-utils')
const { defaults, validate } = require('./options')
module.exports = class Service {
constructor (context, { plugins, pkg, inlineOptions, useBuiltIn } = {}) {
process.VUE_CLI_SERVICE = this
this.initialized = false
this.context = context
this.inlineOptions = inlineOptions
this.webpackChainFns = []
this.webpackRawConfigFns = []
this.devServerConfigFns = []
this.commands = {}
// Folder containing the target package.json for plugins
this.pkgContext = context
// package.json containing the plugins
this.pkg = this.resolvePkg(pkg)
// If there are inline plugins, they will be used instead of those
// found in package.json.
// When useBuiltIn === false, built-in plugins are disabled. This is mostly
// for testing.
this.plugins = this.resolvePlugins(plugins, useBuiltIn)
// pluginsToSkip will be populated during run()
this.pluginsToSkip = new Set()
// resolve the default mode to use for each command
// this is provided by plugins as module.exports.defaultModes
// so we can get the information without actually applying the plugin.
this.modes = this.plugins.reduce((modes, { apply: { defaultModes }}) => {
return Object.assign(modes, defaultModes)
}, {})
}
resolvePkg (inlinePkg, context = this.context) {
if (inlinePkg) {
return inlinePkg
}
const pkg = resolvePkg(context)
if (pkg.vuePlugins && pkg.vuePlugins.resolveFrom) {
this.pkgContext = path.resolve(context, pkg.vuePlugins.resolveFrom)
return this.resolvePkg(null, this.pkgContext)
}
return pkg
}
init (mode = process.env.VUE_CLI_MODE) {
if (this.initialized) {
return
}
this.initialized = true
this.mode = mode
// load mode .env
if (mode) {
this.loadEnv(mode)
}
// load base .env
this.loadEnv()
// load user config
const userOptions = this.loadUserOptions()
this.projectOptions = defaultsDeep(userOptions, defaults())
debug('vue:project-config')(this.projectOptions)
// apply plugins.
this.plugins.forEach(({ id, apply }) => {
if (this.pluginsToSkip.has(id)) return
apply(new PluginAPI(id, this), this.projectOptions)
})
// apply webpack configs from project config file
if (this.projectOptions.chainWebpack) {
this.webpackChainFns.push(this.projectOptions.chainWebpack)
}
if (this.projectOptions.configureWebpack) {
this.webpackRawConfigFns.push(this.projectOptions.configureWebpack)
}
}
loadEnv (mode) {
const logger = debug('vue:env')
const basePath = path.resolve(this.context, `.env${mode ? `.${mode}` : ``}`)
const localPath = `${basePath}.local`
const load = envPath => {
try {
const env = dotenv.config({ path: envPath, debug: process.env.DEBUG })
dotenvExpand(env)
logger(envPath, env)
} catch (err) {
// only ignore error if file is not found
if (err.toString().indexOf('ENOENT') < 0) {
error(err)
}
}
}
load(localPath)
load(basePath)
// by default, NODE_ENV and BABEL_ENV are set to "development" unless mode
// is production or test. However the value in .env files will take higher
// priority.
if (mode) {
// always set NODE_ENV during tests
// as that is necessary for tests to not be affected by each other
const shouldForceDefaultEnv = (
process.env.VUE_CLI_TEST &&
!process.env.VUE_CLI_TEST_TESTING_ENV
)
const defaultNodeEnv = (mode === 'production' || mode === 'test')
? mode
: 'development'
if (shouldForceDefaultEnv || process.env.NODE_ENV == null) {
process.env.NODE_ENV = defaultNodeEnv
}
if (shouldForceDefaultEnv || process.env.BABEL_ENV == null) {
process.env.BABEL_ENV = defaultNodeEnv
}
}
}
setPluginsToSkip (args) {
const skipPlugins = args['skip-plugins']
const pluginsToSkip = skipPlugins
? new Set(skipPlugins.split(',').map(id => resolvePluginId(id)))
: new Set()
this.pluginsToSkip = pluginsToSkip
}
resolvePlugins (inlinePlugins, useBuiltIn) {
const idToPlugin = id => ({
id: id.replace(/^.\//, 'built-in:'),
apply: require(id)
})
let plugins
const builtInPlugins = [
'./commands/serve',
'./commands/build',
'./commands/inspect',
'./commands/help',
// config plugins are order sensitive
'./config/base',
'./config/css',
'./config/prod',
'./config/app'
].map(idToPlugin)
if (inlinePlugins) {
plugins = useBuiltIn !== false
? builtInPlugins.concat(inlinePlugins)
: inlinePlugins
} else {
const projectPlugins = Object.keys(this.pkg.devDependencies || {})
.concat(Object.keys(this.pkg.dependencies || {}))
.filter(isPlugin)
.map(id => {
if (
this.pkg.optionalDependencies &&
id in this.pkg.optionalDependencies
) {
let apply = () => {}
try {
apply = require(id)
} catch (e) {
warn(`Optional dependency ${id} is not installed.`)
}
return { id, apply }
} else {
return idToPlugin(id)
}
})
plugins = builtInPlugins.concat(projectPlugins)
}
// Local plugins
if (this.pkg.vuePlugins && this.pkg.vuePlugins.service) {
const files = this.pkg.vuePlugins.service
if (!Array.isArray(files)) {
throw new Error(`Invalid type for option 'vuePlugins.service', expected 'array' but got ${typeof files}.`)
}
plugins = plugins.concat(files.map(file => ({
id: `local:${file}`,
apply: loadModule(`./${file}`, this.pkgContext)
})))
}
return plugins
}
async run (name, args = {}, rawArgv = []) {
// resolve mode
// prioritize inline --mode
// fallback to resolved default modes from plugins or development if --watch is defined
const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name])
// --skip-plugins arg may have plugins that should be skipped during init()
this.setPluginsToSkip(args)
// load env variables, load user config, apply plugins
this.init(mode)
args._ = args._ || []
let command = this.commands[name]
if (!command && name) {
error(`command "${name}" does not exist.`)
process.exit(1)
}
if (!command || args.help || args.h) {
command = this.commands.help
} else {
args._.shift() // remove command itself
rawArgv.shift()
}
const { fn } = command
return fn(args, rawArgv)
}
resolveChainableWebpackConfig () {
const chainableConfig = new Config()
// apply chains
this.webpackChainFns.forEach(fn => fn(chainableConfig))
return chainableConfig
}
resolveWebpackConfig (chainableConfig = this.resolveChainableWebpackConfig()) {
if (!this.initialized) {
throw new Error('Service must call init() before calling resolveWebpackConfig().')
}
// get raw config
let config = chainableConfig.toConfig()
const original = config
// apply raw config fns
this.webpackRawConfigFns.forEach(fn => {
if (typeof fn === 'function') {
// function with optional return value
const res = fn(config)
if (res) config = merge(config, res)
} else if (fn) {
// merge literal values
config = merge(config, fn)
}
})
// #2206 If config is merged by merge-webpack, it discards the __ruleNames
// information injected by webpack-chain. Restore the info so that
// vue inspect works properly.
if (config !== original) {
cloneRuleNames(
config.module && config.module.rules,
original.module && original.module.rules
)
}
// check if the user has manually mutated output.publicPath
const target = process.env.VUE_CLI_BUILD_TARGET
if (
!process.env.VUE_CLI_TEST &&
(target && target !== 'app') &&
config.output.publicPath !== this.projectOptions.publicPath
) {
throw new Error(
`Do not modify webpack output.publicPath directly. ` +
`Use the "publicPath" option in vue.config.js instead.`
)
}
if (
!process.env.VUE_CLI_ENTRY_FILES &&
typeof config.entry !== 'function'
) {
let entryFiles
if (typeof config.entry === 'string') {
entryFiles = [config.entry]
} else if (Array.isArray(config.entry)) {
entryFiles = config.entry
} else {
entryFiles = Object.values(config.entry || []).reduce((allEntries, curr) => {
return allEntries.concat(curr)
}, [])
}
entryFiles = entryFiles.map(file => path.resolve(this.context, file))
process.env.VUE_CLI_ENTRY_FILES = JSON.stringify(entryFiles)
}
return config
}
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'
}
if (resolved.css && typeof resolved.css.modules !== 'undefined') {
if (typeof resolved.css.requireModuleExtension !== 'undefined') {
warn(
`You have set both "css.modules" and "css.requireModuleExtension" in ${chalk.bold('vue.config.js')}, ` +
`"css.modules" will be ignored in favor of "css.requireModuleExtension".`
)
} else {
warn(
`"css.modules" option in ${chalk.bold('vue.config.js')} ` +
`is deprecated now, please use "css.requireModuleExtension" instead.`
)
resolved.css.requireModuleExtension = !resolved.css.modules
}
}
// normalize some options
ensureSlash(resolved, 'publicPath')
if (typeof resolved.publicPath === 'string') {
resolved.publicPath = resolved.publicPath.replace(/^\.\//, '')
}
removeSlash(resolved, 'outputDir')
// validate options
validate(resolved, msg => {
error(
`Invalid options in ${chalk.bold(resolvedFrom)}: ${msg}`
)
})
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, '')
}
}
function cloneRuleNames (to, from) {
if (!to || !from) {
return
}
from.forEach((r, i) => {
if (to[i]) {
Object.defineProperty(to[i], '__ruleNames', {
value: r.__ruleNames
})
cloneRuleNames(to[i].oneOf, r.oneOf)
}
})
}
@@ -0,0 +1,10 @@
<meta charset="utf-8">
<title><%- htmlWebpackPlugin.options.libName %> demo</title>
<script src="./<%- htmlWebpackPlugin.options.assetsFileName %>.umd.js"></script>
<% if (htmlWebpackPlugin.options.cssExtract) { %>
<link rel="stylesheet" href="./<%- htmlWebpackPlugin.options.assetsFileName %>.css">
<% } %>
<script>
console.log(<%- htmlWebpackPlugin.options.libName %>)
</script>
@@ -0,0 +1,19 @@
<meta charset="utf-8">
<title><%- htmlWebpackPlugin.options.libName %> demo</title>
<script src="https://unpkg.com/vue"></script>
<script src="./<%- htmlWebpackPlugin.options.assetsFileName %>.umd.js"></script>
<% if (htmlWebpackPlugin.options.cssExtract) { %>
<link rel="stylesheet" href="./<%- htmlWebpackPlugin.options.assetsFileName %>.css">
<% } %>
<div id="app">
<demo></demo>
</div>
<script>
new Vue({
components: {
demo: <%- htmlWebpackPlugin.options.libName %>
}
}).$mount('#app')
</script>
@@ -0,0 +1,8 @@
<meta charset="utf-8">
<title><%- htmlWebpackPlugin.options.libName %> demo</title>
<script src="https://unpkg.com/vue"></script>
<script src="./<%- htmlWebpackPlugin.options.libName %>.js"></script>
<% for (const comp of htmlWebpackPlugin.options.components) { %>
<<%= comp %>></<%= comp %>>
<% } %>
@@ -0,0 +1,2 @@
import './setPublicPath'
export * from '~entry'
@@ -0,0 +1,4 @@
import './setPublicPath'
import mod from '~entry'
export default mod
export * from '~entry'
@@ -0,0 +1,72 @@
module.exports = function formatStats (stats, dir, api) {
const fs = require('fs')
const path = require('path')
const zlib = require('zlib')
const ui = require('cliui')({ width: 80 })
const { chalk } = require('@vue/cli-shared-utils')
const json = stats.toJson({
hash: false,
modules: false,
chunks: false
})
let assets = json.assets
? json.assets
: json.children.reduce((acc, child) => acc.concat(child.assets), [])
const seenNames = new Map()
const isJS = val => /\.js$/.test(val)
const isCSS = val => /\.css$/.test(val)
const isMinJS = val => /\.min\.js$/.test(val)
assets = assets
.map(a => {
a.name = a.name.split('?')[0]
return a
})
.filter(a => {
if (seenNames.has(a.name)) {
return false
}
seenNames.set(a.name, true)
return isJS(a.name) || isCSS(a.name)
})
.sort((a, b) => {
if (isJS(a.name) && isCSS(b.name)) return -1
if (isCSS(a.name) && isJS(b.name)) return 1
if (isMinJS(a.name) && !isMinJS(b.name)) return -1
if (!isMinJS(a.name) && isMinJS(b.name)) return 1
return b.size - a.size
})
function formatSize (size) {
return (size / 1024).toFixed(2) + ' KiB'
}
function getGzippedSize (asset) {
const filepath = api.resolve(path.join(dir, asset.name))
const buffer = fs.readFileSync(filepath)
return formatSize(zlib.gzipSync(buffer).length)
}
function makeRow (a, b, c) {
return ` ${a}\t ${b}\t ${c}`
}
ui.div(
makeRow(
chalk.cyan.bold(`File`),
chalk.cyan.bold(`Size`),
chalk.cyan.bold(`Gzipped`)
) + `\n\n` +
assets.map(asset => makeRow(
/js$/.test(asset.name)
? chalk.green(path.join(dir, asset.name))
: chalk.blue(path.join(dir, asset.name)),
formatSize(asset.size),
getGzippedSize(asset)
)).join(`\n`)
)
return `${ui.toString()}\n\n ${chalk.gray(`Images and other types of assets omitted.`)}\n`
}
+238
View File
@@ -0,0 +1,238 @@
const defaults = {
clean: true,
target: 'app',
formats: 'commonjs,umd,umd-min',
'unsafe-inline': true
}
const buildModes = {
lib: 'library',
wc: 'web component',
'wc-async': 'web component (async)'
}
const modifyConfig = (config, fn) => {
if (Array.isArray(config)) {
config.forEach(c => fn(c))
} else {
fn(config)
}
}
module.exports = (api, options) => {
api.registerCommand('build', {
description: 'build for production',
usage: 'vue-cli-service build [options] [entry|pattern]',
options: {
'--mode': `specify env mode (default: production)`,
'--dest': `specify output directory (default: ${options.outputDir})`,
'--modern': `build app targeting modern browsers with auto fallback`,
'--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',
'--formats': `list of output formats for library builds (default: ${defaults.formats})`,
'--name': `name for lib or web-component mode (default: "name" in package.json or entry filename)`,
'--filename': `file name for output, only usable for 'lib' target (default: value of --name)`,
'--no-clean': `do not remove the dist directory before building the project`,
'--report': `generate report.html to help analyze bundle content`,
'--report-json': 'generate report.json to help analyze bundle content',
'--skip-plugins': `comma-separated list of plugin names to skip for this run`,
'--watch': `watch for changes`,
'--stdin': `close when stdin ends`
}
}, async (args, rawArgs) => {
for (const key in defaults) {
if (args[key] == null) {
args[key] = defaults[key]
}
}
args.entry = args.entry || args._[0]
if (args.target !== 'app') {
args.entry = args.entry || 'src/App.vue'
}
process.env.VUE_CLI_BUILD_TARGET = args.target
if (args.modern && args.target === 'app') {
process.env.VUE_CLI_MODERN_MODE = true
if (!process.env.VUE_CLI_MODERN_BUILD) {
// main-process for legacy build
await build(Object.assign({}, args, {
modernBuild: false,
keepAlive: true
}), api, options)
// spawn sub-process of self for modern build
const { execa } = require('@vue/cli-shared-utils')
const cliBin = require('path').resolve(__dirname, '../../../bin/vue-cli-service.js')
await execa('node', [cliBin, 'build', ...rawArgs], {
stdio: 'inherit',
env: {
VUE_CLI_MODERN_BUILD: true
}
})
} else {
// sub-process for modern build
await build(Object.assign({}, args, {
modernBuild: true,
clean: false
}), 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
})
}
async function build (args, api, options) {
const fs = require('fs-extra')
const path = require('path')
const webpack = require('webpack')
const { chalk } = require('@vue/cli-shared-utils')
const formatStats = require('./formatStats')
const validateWebpackConfig = require('../../util/validateWebpackConfig')
const {
log,
done,
info,
logWithSpinner,
stopSpinner
} = require('@vue/cli-shared-utils')
log()
const mode = api.service.mode
if (args.target === 'app') {
const bundleTag = args.modern
? args.modernBuild
? `modern bundle `
: `legacy bundle `
: ``
logWithSpinner(`Building ${bundleTag}for ${mode}...`)
} else {
const buildMode = buildModes[args.target]
if (buildMode) {
const additionalParams = buildMode === 'library' ? ` (${args.formats})` : ``
logWithSpinner(`Building for ${mode} as ${buildMode}${additionalParams}...`)
} else {
throw new Error(`Unknown build target: ${args.target}`)
}
}
if (args.dest) {
// Override outputDir before resolving webpack config as config relies on it (#2327)
options.outputDir = args.dest
}
const targetDir = api.resolve(options.outputDir)
const isLegacyBuild = args.target === 'app' && args.modern && !args.modernBuild
// resolve raw webpack config
let webpackConfig
if (args.target === 'lib') {
webpackConfig = require('./resolveLibConfig')(api, args, options)
} else if (
args.target === 'wc' ||
args.target === 'wc-async'
) {
webpackConfig = require('./resolveWcConfig')(api, args, options)
} else {
webpackConfig = require('./resolveAppConfig')(api, args, options)
}
// check for common config errors
validateWebpackConfig(webpackConfig, api, options, args.target)
if (args.watch) {
modifyConfig(webpackConfig, config => {
config.watch = true
})
}
if (args.stdin) {
process.stdin.on('end', () => {
process.exit(0)
})
process.stdin.resume()
}
// Expose advanced stats
if (args.dashboard) {
const DashboardPlugin = require('../../webpack/DashboardPlugin')
modifyConfig(webpackConfig, config => {
config.plugins.push(new DashboardPlugin({
type: 'build',
modernBuild: args.modernBuild,
keepAlive: args.keepAlive
}))
})
}
if (args.report || args['report-json']) {
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
modifyConfig(webpackConfig, config => {
const bundleName = args.target !== 'app'
? config.output.filename.replace(/\.js$/, '-')
: isLegacyBuild ? 'legacy-' : ''
config.plugins.push(new BundleAnalyzerPlugin({
logLevel: 'warn',
openAnalyzer: false,
analyzerMode: args.report ? 'static' : 'disabled',
reportFilename: `${bundleName}report.html`,
statsFilename: `${bundleName}report.json`,
generateStatsFile: !!args['report-json']
}))
})
}
if (args.clean) {
await fs.remove(targetDir)
}
return new Promise((resolve, reject) => {
webpack(webpackConfig, (err, stats) => {
stopSpinner(false)
if (err) {
return reject(err)
}
if (stats.hasErrors()) {
return reject(`Build failed with errors.`)
}
if (!args.silent) {
const targetDirShort = path.relative(
api.service.context,
targetDir
)
log(formatStats(stats, targetDirShort, api))
if (args.target === 'app' && !isLegacyBuild) {
if (!args.watch) {
done(`Build complete. The ${chalk.cyan(targetDirShort)} directory is ready to be deployed.`)
info(`Check out deployment instructions at ${chalk.cyan(`https://cli.vuejs.org/guide/deployment.html`)}\n`)
} else {
done(`Build complete. Watching for changes...`)
}
}
}
// test-only signal
if (process.env.VUE_CLI_TEST) {
console.log('Build complete.')
}
resolve()
})
})
}
module.exports.defaultModes = {
build: 'production'
}
@@ -0,0 +1,47 @@
module.exports = (api, args, options) => {
// respect inline entry
if (args.entry && !options.pages) {
api.configureWebpack(config => {
config.entry = { app: api.resolve(args.entry) }
})
}
const config = api.resolveChainableWebpackConfig()
const targetDir = api.resolve(args.dest || options.outputDir)
// respect inline build destination in copy plugin
if (args.dest && config.plugins.has('copy')) {
config.plugin('copy').tap(pluginArgs => {
pluginArgs[0][0].to = targetDir
return pluginArgs
})
}
if (args.modern) {
const ModernModePlugin = require('../../webpack/ModernModePlugin')
if (!args.modernBuild) {
// Inject plugin to extract build stats and write to disk
config
.plugin('modern-mode-legacy')
.use(ModernModePlugin, [{
targetDir,
isModernBuild: false,
unsafeInline: args['unsafe-inline']
}])
} else {
// Inject plugin to read non-modern build stats and inject HTML
config
.plugin('modern-mode-modern')
.use(ModernModePlugin, [{
targetDir,
isModernBuild: true,
unsafeInline: args['unsafe-inline'],
// as we may generate an addition file asset (if `no-unsafe-inline` specified)
// we need to provide the correct directory for that file to place in
jsDirectory: require('../../util/getAssetPath')(options, 'js')
}])
}
}
return api.resolveWebpackConfig(config)
}
@@ -0,0 +1,151 @@
const fs = require('fs')
const path = require('path')
module.exports = (api, { entry, name, formats, filename, 'inline-vue': inlineVue }, options) => {
const { log, error } = require('@vue/cli-shared-utils')
const abort = msg => {
log()
error(msg)
process.exit(1)
}
const fullEntryPath = api.resolve(entry)
if (!fs.existsSync(fullEntryPath)) {
abort(
`Failed to resolve lib entry: ${entry}${entry === `src/App.vue` ? ' (default)' : ''}. ` +
`Make sure to specify the correct entry file.`
)
}
const isVueEntry = /\.vue$/.test(entry)
const libName = (
name ||
(
api.service.pkg.name
? api.service.pkg.name.replace(/^@.+\//, '')
: path.basename(entry).replace(/\.(jsx?|vue)$/, '')
)
)
filename = filename || libName
function genConfig (format, postfix = format, genHTML) {
const config = api.resolveChainableWebpackConfig()
const browserslist = require('browserslist')
const targets = browserslist(undefined, { path: fullEntryPath })
const supportsIE = targets.some(agent => agent.includes('ie'))
const webpack = require('webpack')
config.plugin('need-current-script-polyfill')
.use(webpack.DefinePlugin, [{
'process.env.NEED_CURRENTSCRIPT_POLYFILL': JSON.stringify(supportsIE)
}])
// adjust css output name so they write to the same file
if (config.plugins.has('extract-css')) {
config
.plugin('extract-css')
.tap(args => {
args[0].filename = `${filename}.css`
return args
})
}
// only minify min entry
if (!/\.min/.test(postfix)) {
config.optimization.minimize(false)
}
// inject demo page for umd
if (genHTML) {
const template = isVueEntry ? 'demo-lib.html' : 'demo-lib-js.html'
config
.plugin('demo-html')
.use(require('html-webpack-plugin'), [{
template: path.resolve(__dirname, template),
inject: false,
filename: 'demo.html',
libName,
assetsFileName: filename,
cssExtract: config.plugins.has('extract-css')
}])
}
// resolve entry/output
const entryName = `${filename}.${postfix}`
config.resolve
.alias
.set('~entry', fullEntryPath)
// set output target before user configureWebpack hooks are applied
config.output.libraryTarget(format)
// set entry/output after user configureWebpack hooks are applied
const rawConfig = api.resolveWebpackConfig(config)
let realEntry = require.resolve('./entry-lib.js')
// avoid importing default if user entry file does not have default export
if (!isVueEntry) {
const entryContent = fs.readFileSync(fullEntryPath, 'utf-8')
if (!/\b(export\s+default|export\s{[^}]+as\s+default)\b/.test(entryContent)) {
realEntry = require.resolve('./entry-lib-no-default.js')
}
}
// externalize Vue in case user imports it
rawConfig.externals = [
...(Array.isArray(rawConfig.externals) ? rawConfig.externals : [rawConfig.externals]),
{
...(inlineVue || {
vue: {
commonjs: 'vue',
commonjs2: 'vue',
root: 'Vue'
}
})
}
].filter(Boolean)
rawConfig.entry = {
[entryName]: realEntry
}
rawConfig.output = Object.assign({
library: libName,
libraryExport: isVueEntry ? 'default' : undefined,
libraryTarget: format,
// preserve UDM header from webpack 3 until webpack provides either
// libraryTarget: 'esm' or target: 'universal'
// https://github.com/webpack/webpack/issues/6522
// https://github.com/webpack/webpack/issues/6525
globalObject: `(typeof self !== 'undefined' ? self : this)`
}, rawConfig.output, {
filename: `${entryName}.js`,
chunkFilename: `${entryName}.[name].js`,
// use dynamic publicPath so this can be deployed anywhere
// the actual path will be determined at runtime by checking
// document.currentScript.src.
publicPath: ''
})
return rawConfig
}
const configMap = {
commonjs: genConfig('commonjs2', 'common'),
umd: genConfig('umd', undefined, true),
'umd-min': genConfig('umd', 'umd.min')
}
const formatArray = (formats + '').split(',')
const configs = formatArray.map(format => configMap[format])
if (configs.indexOf(undefined) !== -1) {
const unknownFormats = formatArray.filter(f => configMap[f] === undefined).join(', ')
abort(
`Unknown library build formats: ${unknownFormats}`
)
}
return configs
}
@@ -0,0 +1,128 @@
const path = require('path')
const { resolveEntry, fileToComponentName } = require('./resolveWcEntry')
module.exports = (api, { target, entry, name, 'inline-vue': inlineVue }) => {
// Disable CSS extraction and turn on CSS shadow mode for vue-style-loader
process.env.VUE_CLI_CSS_SHADOW_MODE = true
const { log, error } = require('@vue/cli-shared-utils')
const abort = msg => {
log()
error(msg)
process.exit(1)
}
const isAsync = /async/.test(target)
// generate dynamic entry based on glob files
const resolvedFiles = require('globby').sync(entry.split(','), { cwd: api.resolve('.') })
if (!resolvedFiles.length) {
abort(`entry pattern "${entry}" did not match any files.`)
}
let libName
let prefix
if (resolvedFiles.length === 1) {
// in single mode, determine the lib name from filename
libName = name || fileToComponentName('', resolvedFiles[0]).kebabName
prefix = ''
if (libName.indexOf('-') < 0) {
abort(`--name must contain a hyphen when building a single web component.`)
}
} else {
// multi mode
libName = prefix = (name || api.service.pkg.name)
if (!libName) {
abort(`--name is required when building multiple web components.`)
}
}
const dynamicEntry = resolveEntry(prefix, libName, resolvedFiles, isAsync)
function genConfig (minify, genHTML) {
const config = api.resolveChainableWebpackConfig()
// make sure not to transpile wc-wrapper
config.module
.rule('js')
.exclude
.add(/vue-wc-wrapper/)
// only minify min entry
if (!minify) {
config.optimization.minimize(false)
}
config
.plugin('web-component-options')
.use(require('webpack').DefinePlugin, [{
'process.env.CUSTOM_ELEMENT_NAME': JSON.stringify(libName)
}])
// enable shadow mode in vue-loader
config.module
.rule('vue')
.use('vue-loader')
.tap(options => {
options.shadowMode = true
return options
})
if (genHTML) {
config
.plugin('demo-html')
.use(require('html-webpack-plugin'), [{
template: path.resolve(__dirname, `./demo-wc.html`),
inject: false,
filename: 'demo.html',
libName,
components:
prefix === ''
? [libName]
: resolvedFiles.map(file => {
return fileToComponentName(prefix, file).kebabName
})
}])
}
// set entry/output last so it takes higher priority than user
// configureWebpack hooks
// set proxy entry for *.vue files
config.resolve
.alias
.set('~root', api.resolve('.'))
const rawConfig = api.resolveWebpackConfig(config)
// externalize Vue in case user imports it
rawConfig.externals = [
...(Array.isArray(rawConfig.externals) ? rawConfig.externals : [rawConfig.externals]),
{ ...(inlineVue || { vue: 'Vue' }) }
].filter(Boolean)
const entryName = `${libName}${minify ? `.min` : ``}`
rawConfig.entry = {
[entryName]: dynamicEntry
}
Object.assign(rawConfig.output, {
// to ensure that multiple copies of async wc bundles can co-exist
// on the same page.
jsonpFunction: libName.replace(/-\w/g, c => c.charAt(1).toUpperCase()) + '_jsonp',
filename: `${entryName}.js`,
chunkFilename: `${libName}.[name]${minify ? `.min` : ``}.js`,
// use dynamic publicPath so this can be deployed anywhere
// the actual path will be determined at runtime by checking
// document.currentScript.src.
publicPath: ''
})
return rawConfig
}
return [
genConfig(false, true),
genConfig(true, false)
]
}
@@ -0,0 +1,61 @@
const fs = require('fs')
const path = require('path')
const camelizeRE = /-(\w)/g
const camelize = str => {
return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
}
const hyphenateRE = /\B([A-Z])/g
const hyphenate = str => {
return str.replace(hyphenateRE, '-$1').toLowerCase()
}
/**
* Creates the script to add the component to the custom elements
* @param {string} prefix The prefix for the component library
* @param {string} component The component name for single entry builds, component file for multi-entry builds
* @param {string} file The file for the component
* @param {boolean} isAsync Whether to load component async or not
*/
const createElement = (prefix, component, file, isAsync) => {
const { camelName, kebabName } = exports.fileToComponentName(prefix, component)
return isAsync
? `window.customElements.define('${kebabName}', wrap(Vue, () => import('~root/${file}?shadow')))\n`
: `import ${camelName} from '~root/${file}?shadow'\n` +
`window.customElements.define('${kebabName}', wrap(Vue, ${camelName}))\n`
}
exports.fileToComponentName = (prefix, file) => {
const basename = path.basename(file).replace(/\.(jsx?|vue)$/, '')
const camelName = camelize(basename)
const kebabName = `${prefix ? `${prefix}-` : ``}${hyphenate(basename)}`
return {
basename,
camelName,
kebabName
}
}
exports.resolveEntry = (prefix, libName, files, isAsync) => {
const filePath = path.resolve(__dirname, 'entry-wc.js')
const elements =
prefix === ''
? [createElement('', libName, files[0])]
: files.map(file => createElement(prefix, file, file, isAsync)).join('\n')
const content = `
import './setPublicPath'
import Vue from 'vue'
import wrap from '@vue/web-component-wrapper'
// runtime shared by every component chunk
import 'css-loader/dist/runtime/api.js'
import 'vue-style-loader/lib/addStylesShadow'
import 'vue-loader/lib/runtime/componentNormalizer'
${elements}`.trim()
fs.writeFileSync(filePath, content)
return filePath
}
@@ -0,0 +1,22 @@
// This file is imported into lib/wc client bundles.
if (typeof window !== 'undefined') {
var currentScript = window.document.currentScript
if (process.env.NEED_CURRENTSCRIPT_POLYFILL) {
var getCurrentScript = require('@soda/get-current-script')
currentScript = getCurrentScript()
// for backward compatibility, because previously we directly included the polyfill
if (!('currentScript' in document)) {
Object.defineProperty(document, 'currentScript', { get: getCurrentScript })
}
}
var src = currentScript && currentScript.src.match(/(.+\/)[^/]+\.js(\?.*)?$/)
if (src) {
__webpack_public_path__ = src[1] // eslint-disable-line
}
}
// Indicate to webpack that this file can be concatenated
export default null
+62
View File
@@ -0,0 +1,62 @@
const { chalk } = require('@vue/cli-shared-utils')
const getPadLength = require('../util/getPadLength')
module.exports = (api, options) => {
api.registerCommand('help', args => {
const commandName = args._[0]
if (!commandName) {
logMainHelp()
} else {
logHelpForCommand(commandName, api.service.commands[commandName])
}
})
function logMainHelp () {
console.log(
`\n Usage: vue-cli-service <command> [options]\n` +
`\n Commands:\n`
)
const commands = api.service.commands
const padLength = getPadLength(commands)
for (const name in commands) {
if (name !== 'help') {
const opts = commands[name].opts || {}
console.log(` ${
chalk.blue(name.padEnd(padLength))
}${
opts.description || ''
}`)
}
}
console.log(`\n run ${
chalk.green(`vue-cli-service help [command]`)
} for usage of a specific command.\n`)
}
function logHelpForCommand (name, command) {
if (!command) {
console.log(chalk.red(`\n command "${name}" does not exist.`))
} else {
const opts = command.opts || {}
if (opts.usage) {
console.log(`\n Usage: ${opts.usage}`)
}
if (opts.options) {
console.log(`\n Options:\n`)
const padLength = getPadLength(opts.options)
for (const [flags, description] of Object.entries(opts.options)) {
console.log(` ${
chalk.blue(flags.padEnd(padLength))
}${
description
}`)
}
}
if (opts.details) {
console.log()
console.log(opts.details.split('\n').map(line => ` ${line}`).join('\n'))
}
console.log()
}
}
}
+74
View File
@@ -0,0 +1,74 @@
module.exports = (api, options) => {
api.registerCommand(
'inspect',
{
description: 'inspect internal webpack config',
usage: 'vue-cli-service inspect [options] [...paths]',
options: {
'--mode': 'specify env mode (default: development)',
'--rule <ruleName>': 'inspect a specific module rule',
'--plugin <pluginName>': 'inspect a specific plugin',
'--rules': 'list all module rule names',
'--plugins': 'list all plugin names',
'--verbose': 'show full function definitions in output',
'--skip-plugins': 'comma-separated list of plugin names to skip for this run'
}
},
args => {
const { chalk, get } = require('@vue/cli-shared-utils')
const { toString } = require('webpack-chain')
const { highlight } = require('cli-highlight')
const config = api.resolveWebpackConfig()
const { _: paths, verbose } = args
let res
let hasUnnamedRule
if (args.rule) {
res = config.module.rules.find(r => r.__ruleNames[0] === args.rule)
} else if (args.plugin) {
res = config.plugins.find(p => p.__pluginName === args.plugin)
} else if (args.rules) {
res = config.module.rules.map(r => {
const name = r.__ruleNames ? r.__ruleNames[0] : 'Nameless Rule (*)'
hasUnnamedRule = hasUnnamedRule || !r.__ruleNames
return name
})
} else if (args.plugins) {
res = config.plugins.map(p => p.__pluginName || p.constructor.name)
} else if (paths.length > 1) {
res = {}
paths.forEach(path => {
res[path] = get(config, path)
})
} else if (paths.length === 1) {
res = get(config, paths[0])
} else {
res = config
}
const output = toString(res, { verbose })
console.log(highlight(output, { language: 'js' }))
// Log explanation for Nameless Rules
if (hasUnnamedRule) {
console.log(`--- ${chalk.green('Footnotes')} ---`)
console.log(`*: ${chalk.green(
'Nameless Rules'
)} were added through the ${chalk.green(
'configureWebpack()'
)} API (possibly by a plugin) instead of ${chalk.green(
'chainWebpack()'
)} (recommended).
You can run ${chalk.green(
'vue-cli-service inspect'
)} without any arguments to inspect the full config and read these rules' config.`)
}
}
)
}
module.exports.defaultModes = {
inspect: 'development'
}
+374
View File
@@ -0,0 +1,374 @@
const {
info,
error,
hasProjectYarn,
hasProjectPnpm,
openBrowser,
IpcMessenger
} = require('@vue/cli-shared-utils')
const defaults = {
host: '0.0.0.0',
port: 8080,
https: false
}
module.exports = (api, options) => {
api.registerCommand('serve', {
description: 'start development server',
usage: 'vue-cli-service serve [options] [entry]',
options: {
'--open': `open browser on server start`,
'--copy': `copy url to clipboard on server start`,
'--stdin': `close when stdin ends`,
'--mode': `specify env mode (default: development)`,
'--host': `specify host (default: ${defaults.host})`,
'--port': `specify port (default: ${defaults.port})`,
'--https': `use https (default: ${defaults.https})`,
'--public': `specify the public network URL for the HMR client`,
'--skip-plugins': `comma-separated list of plugin names to skip for this run`
}
}, async function serve (args) {
info('Starting development server...')
// although this is primarily a dev server, it is possible that we
// are running it in a mode with a production env, e.g. in E2E tests.
const isInContainer = checkInContainer()
const isProduction = process.env.NODE_ENV === 'production'
const url = require('url')
const { chalk } = require('@vue/cli-shared-utils')
const webpack = require('webpack')
const WebpackDevServer = require('webpack-dev-server')
const portfinder = require('portfinder')
const prepareURLs = require('../util/prepareURLs')
const prepareProxy = require('../util/prepareProxy')
const launchEditorMiddleware = require('launch-editor-middleware')
const validateWebpackConfig = require('../util/validateWebpackConfig')
const isAbsoluteUrl = require('../util/isAbsoluteUrl')
// configs that only matters for dev server
api.chainWebpack(webpackConfig => {
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
webpackConfig
.devtool('cheap-module-eval-source-map')
webpackConfig
.plugin('hmr')
.use(require('webpack/lib/HotModuleReplacementPlugin'))
// https://github.com/webpack/webpack/issues/6642
// https://github.com/vuejs/vue-cli/issues/3539
webpackConfig
.output
.globalObject(`(typeof self !== 'undefined' ? self : this)`)
if (!process.env.VUE_CLI_TEST && options.devServer.progress !== false) {
webpackConfig
.plugin('progress')
.use(require('webpack/lib/ProgressPlugin'))
}
}
})
// resolve webpack config
const webpackConfig = api.resolveWebpackConfig()
// check for common config errors
validateWebpackConfig(webpackConfig, api, options)
// load user devServer options with higher priority than devServer
// in webpack config
const projectDevServerOptions = Object.assign(
webpackConfig.devServer || {},
options.devServer
)
// expose advanced stats
if (args.dashboard) {
const DashboardPlugin = require('../webpack/DashboardPlugin')
;(webpackConfig.plugins = webpackConfig.plugins || []).push(new DashboardPlugin({
type: 'serve'
}))
}
// entry arg
const entry = args._[0]
if (entry) {
webpackConfig.entry = {
app: api.resolve(entry)
}
}
// resolve server options
const useHttps = args.https || projectDevServerOptions.https || defaults.https
const protocol = useHttps ? 'https' : 'http'
const host = args.host || process.env.HOST || projectDevServerOptions.host || defaults.host
portfinder.basePort = args.port || process.env.PORT || projectDevServerOptions.port || defaults.port
const port = await portfinder.getPortPromise()
const rawPublicUrl = args.public || projectDevServerOptions.public
const publicUrl = rawPublicUrl
? /^[a-zA-Z]+:\/\//.test(rawPublicUrl)
? rawPublicUrl
: `${protocol}://${rawPublicUrl}`
: null
const urls = prepareURLs(
protocol,
host,
port,
isAbsoluteUrl(options.publicPath) ? '/' : options.publicPath
)
const localUrlForBrowser = publicUrl || urls.localUrlForBrowser
const proxySettings = prepareProxy(
projectDevServerOptions.proxy,
api.resolve('public')
)
// inject dev & hot-reload middleware entries
if (!isProduction) {
const sockPath = projectDevServerOptions.sockPath || '/sockjs-node'
const sockjsUrl = publicUrl
// explicitly configured via devServer.public
? `?${publicUrl}&sockPath=${sockPath}`
: isInContainer
// can't infer public network url if inside a container...
// use client-side inference (note this would break with non-root publicPath)
? ``
// otherwise infer the url
: `?` + url.format({
protocol,
port,
hostname: urls.lanUrlForConfig || 'localhost'
}) + `&sockPath=${sockPath}`
const devClients = [
// dev server client
require.resolve(`webpack-dev-server/client`) + sockjsUrl,
// hmr client
require.resolve(projectDevServerOptions.hotOnly
? 'webpack/hot/only-dev-server'
: 'webpack/hot/dev-server')
// TODO custom overlay client
// `@vue/cli-overlay/dist/client`
]
if (process.env.APPVEYOR) {
devClients.push(`webpack/hot/poll?500`)
}
// inject dev/hot client
addDevClientToEntry(webpackConfig, devClients)
}
// create compiler
const compiler = webpack(webpackConfig)
// handle compiler error
compiler.hooks.failed.tap('vue-cli-service serve', msg => {
error(msg)
process.exit(1)
})
// create server
const server = new WebpackDevServer(compiler, Object.assign({
logLevel: 'silent',
clientLogLevel: 'silent',
historyApiFallback: {
disableDotRule: true,
rewrites: genHistoryApiFallbackRewrites(options.publicPath, options.pages)
},
contentBase: api.resolve('public'),
watchContentBase: !isProduction,
hot: !isProduction,
injectClient: false,
compress: isProduction,
publicPath: options.publicPath,
overlay: isProduction // TODO disable this
? false
: { warnings: false, errors: true }
}, projectDevServerOptions, {
https: useHttps,
proxy: proxySettings,
// eslint-disable-next-line no-shadow
before (app, server) {
// launch editor support.
// this works with vue-devtools & @vue/cli-overlay
app.use('/__open-in-editor', launchEditorMiddleware(() => console.log(
`To specify an editor, specify the EDITOR env variable or ` +
`add "editor" field to your Vue project config.\n`
)))
// allow other plugins to register middlewares, e.g. PWA
api.service.devServerConfigFns.forEach(fn => fn(app, server))
// apply in project middlewares
projectDevServerOptions.before && projectDevServerOptions.before(app, server)
},
// avoid opening browser
open: false
}))
;['SIGINT', 'SIGTERM'].forEach(signal => {
process.on(signal, () => {
server.close(() => {
process.exit(0)
})
})
})
if (args.stdin) {
process.stdin.on('end', () => {
server.close(() => {
process.exit(0)
})
})
process.stdin.resume()
}
// on appveyor, killing the process with SIGTERM causes execa to
// throw error
if (process.env.VUE_CLI_TEST) {
process.stdin.on('data', data => {
if (data.toString() === 'close') {
console.log('got close signal!')
server.close(() => {
process.exit(0)
})
}
})
}
return new Promise((resolve, reject) => {
// log instructions & open browser on first compilation complete
let isFirstCompile = true
compiler.hooks.done.tap('vue-cli-service serve', stats => {
if (stats.hasErrors()) {
return
}
let copied = ''
if (isFirstCompile && args.copy) {
try {
require('clipboardy').writeSync(localUrlForBrowser)
copied = chalk.dim('(copied to clipboard)')
} catch (_) {
/* catch exception if copy to clipboard isn't supported (e.g. WSL), see issue #3476 */
}
}
const networkUrl = publicUrl
? publicUrl.replace(/([^/])$/, '$1/')
: urls.lanUrlForTerminal
console.log()
console.log(` App running at:`)
console.log(` - Local: ${chalk.cyan(urls.localUrlForTerminal)} ${copied}`)
if (!isInContainer) {
console.log(` - Network: ${chalk.cyan(networkUrl)}`)
} else {
console.log()
console.log(chalk.yellow(` It seems you are running Vue CLI inside a container.`))
if (!publicUrl && options.publicPath && options.publicPath !== '/') {
console.log()
console.log(chalk.yellow(` Since you are using a non-root publicPath, the hot-reload socket`))
console.log(chalk.yellow(` will not be able to infer the correct URL to connect. You should`))
console.log(chalk.yellow(` explicitly specify the URL via ${chalk.blue(`devServer.public`)}.`))
console.log()
}
console.log(chalk.yellow(` Access the dev server via ${chalk.cyan(
`${protocol}://localhost:<your container's external mapped port>${options.publicPath}`
)}`))
}
console.log()
if (isFirstCompile) {
isFirstCompile = false
if (!isProduction) {
const buildCommand = hasProjectYarn(api.getCwd()) ? `yarn build` : hasProjectPnpm(api.getCwd()) ? `pnpm run build` : `npm run build`
console.log(` Note that the development build is not optimized.`)
console.log(` To create a production build, run ${chalk.cyan(buildCommand)}.`)
} else {
console.log(` App is served in production mode.`)
console.log(` Note this is for preview or E2E testing only.`)
}
console.log()
if (args.open || projectDevServerOptions.open) {
const pageUri = (projectDevServerOptions.openPage && typeof projectDevServerOptions.openPage === 'string')
? projectDevServerOptions.openPage
: ''
openBrowser(localUrlForBrowser + pageUri)
}
// Send final app URL
if (args.dashboard) {
const ipc = new IpcMessenger()
ipc.send({
vueServe: {
url: localUrlForBrowser
}
})
}
// resolve returned Promise
// so other commands can do api.service.run('serve').then(...)
resolve({
server,
url: localUrlForBrowser
})
} else if (process.env.VUE_CLI_TEST) {
// signal for test to check HMR
console.log('App updated')
}
})
server.listen(port, host, err => {
if (err) {
reject(err)
}
})
})
})
}
function addDevClientToEntry (config, devClient) {
const { entry } = config
if (typeof entry === 'object' && !Array.isArray(entry)) {
Object.keys(entry).forEach((key) => {
entry[key] = devClient.concat(entry[key])
})
} else if (typeof entry === 'function') {
config.entry = entry(devClient)
} else {
config.entry = devClient.concat(entry)
}
}
// https://stackoverflow.com/a/20012536
function checkInContainer () {
const fs = require('fs')
if (fs.existsSync(`/proc/1/cgroup`)) {
const content = fs.readFileSync(`/proc/1/cgroup`, 'utf-8')
return /:\/(lxc|docker|kubepods)\//.test(content)
}
}
function genHistoryApiFallbackRewrites (baseUrl, pages = {}) {
const path = require('path')
const multiPageRewrites = Object
.keys(pages)
// sort by length in reversed order to avoid overrides
// eg. 'page11' should appear in front of 'page1'
.sort((a, b) => b.length - a.length)
.map(name => ({
from: new RegExp(`^/${name}`),
to: path.posix.join(baseUrl, pages[name].filename || `${name}.html`)
}))
return [
...multiPageRewrites,
{ from: /./, to: path.posix.join(baseUrl, 'index.html') }
]
}
module.exports.defaultModes = {
serve: 'development'
}
+307
View File
@@ -0,0 +1,307 @@
// config that are specific to --target app
const fs = require('fs')
const path = require('path')
// ensure the filename passed to html-webpack-plugin is a relative path
// because it cannot correctly handle absolute paths
function ensureRelative (outputDir, _path) {
if (path.isAbsolute(_path)) {
return path.relative(outputDir, _path)
} else {
return _path
}
}
module.exports = (api, options) => {
api.chainWebpack(webpackConfig => {
// only apply when there's no alternative target
if (process.env.VUE_CLI_BUILD_TARGET && process.env.VUE_CLI_BUILD_TARGET !== 'app') {
return
}
const isProd = process.env.NODE_ENV === 'production'
const isLegacyBundle = process.env.VUE_CLI_MODERN_MODE && !process.env.VUE_CLI_MODERN_BUILD
const outputDir = api.resolve(options.outputDir)
const getAssetPath = require('../util/getAssetPath')
const outputFilename = getAssetPath(
options,
`js/[name]${isLegacyBundle ? `-legacy` : ``}${isProd && options.filenameHashing ? '.[contenthash:8]' : ''}.js`
)
webpackConfig
.output
.filename(outputFilename)
.chunkFilename(outputFilename)
// code splitting
if (process.env.NODE_ENV !== 'test') {
webpackConfig
.optimization.splitChunks({
cacheGroups: {
vendors: {
name: `chunk-vendors`,
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: `chunk-common`,
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
})
}
// HTML plugin
const resolveClientEnv = require('../util/resolveClientEnv')
// #1669 html-webpack-plugin's default sort uses toposort which cannot
// handle cyclic deps in certain cases. Monkey patch it to handle the case
// before we can upgrade to its 4.0 version (incompatible with preload atm)
const chunkSorters = require('html-webpack-plugin/lib/chunksorter')
const depSort = chunkSorters.dependency
chunkSorters.auto = chunkSorters.dependency = (chunks, ...args) => {
try {
return depSort(chunks, ...args)
} catch (e) {
// fallback to a manual sort if that happens...
return chunks.sort((a, b) => {
// make sure user entry is loaded last so user CSS can override
// vendor CSS
if (a.id === 'app') {
return 1
} else if (b.id === 'app') {
return -1
} else if (a.entry !== b.entry) {
return b.entry ? -1 : 1
}
return 0
})
}
}
const htmlOptions = {
title: api.service.pkg.name,
templateParameters: (compilation, assets, pluginOptions) => {
// enhance html-webpack-plugin's built in template params
let stats
return Object.assign({
// make stats lazy as it is expensive
get webpack () {
return stats || (stats = compilation.getStats().toJson())
},
compilation: compilation,
webpackConfig: compilation.options,
htmlWebpackPlugin: {
files: assets,
options: pluginOptions
}
}, resolveClientEnv(options, true /* raw */))
}
}
// handle indexPath
if (options.indexPath !== 'index.html') {
// why not set filename for html-webpack-plugin?
// 1. It cannot handle absolute paths
// 2. Relative paths causes incorrect SW manifest to be generated (#2007)
webpackConfig
.plugin('move-index')
.use(require('../webpack/MovePlugin'), [
path.resolve(outputDir, 'index.html'),
path.resolve(outputDir, options.indexPath)
])
}
if (isProd) {
Object.assign(htmlOptions, {
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
collapseBooleanAttributes: true,
removeScriptTypeAttributes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
}
})
// keep chunk ids stable so async chunks have consistent hash (#1916)
webpackConfig
.plugin('named-chunks')
.use(require('webpack/lib/NamedChunksPlugin'), [chunk => {
if (chunk.name) {
return chunk.name
}
const hash = require('hash-sum')
const joinedHash = hash(
Array.from(chunk.modulesIterable, m => m.id).join('_')
)
return `chunk-` + joinedHash
}])
}
// 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')
const publicCopyIgnore = ['.DS_Store']
if (!multiPageConfig) {
// default, single page setup.
htmlOptions.template = fs.existsSync(htmlPath)
? htmlPath
: defaultHtmlPath
publicCopyIgnore.push({
glob: path.relative(api.resolve('public'), api.resolve(htmlOptions.template)),
matchBase: false
})
webpackConfig
.plugin('html')
.use(HTMLPlugin, [htmlOptions])
if (!isLegacyBundle) {
// 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)
const normalizePageConfig = c => typeof c === 'string' ? { entry: c } : c
pages.forEach(name => {
const pageConfig = normalizePageConfig(multiPageConfig[name])
const {
entry,
template = `public/${name}.html`,
filename = `${name}.html`,
chunks = ['chunk-vendors', 'chunk-common', name]
} = pageConfig
// Currently Cypress v3.1.0 comes with a very old version of Node,
// which does not support object rest syntax.
// (https://github.com/cypress-io/cypress/issues/2253)
// So here we have to extract the customHtmlOptions manually.
const customHtmlOptions = {}
for (const key in pageConfig) {
if (
!['entry', 'template', 'filename', 'chunks'].includes(key)
) {
customHtmlOptions[key] = pageConfig[key]
}
}
// inject entry
const entries = Array.isArray(entry) ? entry : [entry]
webpackConfig.entry(name).merge(entries.map(e => api.resolve(e)))
// resolve page index template
const hasDedicatedTemplate = fs.existsSync(api.resolve(template))
const templatePath = hasDedicatedTemplate
? template
: fs.existsSync(htmlPath)
? htmlPath
: defaultHtmlPath
publicCopyIgnore.push({
glob: path.relative(api.resolve('public'), api.resolve(templatePath)),
matchBase: false
})
// inject html plugin for the page
const pageHtmlOptions = Object.assign(
{},
htmlOptions,
{
chunks,
template: templatePath,
filename: ensureRelative(outputDir, filename)
},
customHtmlOptions
)
webpackConfig
.plugin(`html-${name}`)
.use(HTMLPlugin, [pageHtmlOptions])
})
if (!isLegacyBundle) {
pages.forEach(name => {
const filename = ensureRelative(
outputDir,
normalizePageConfig(multiPageConfig[name]).filename || `${name}.html`
)
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]
}
}])
})
}
}
// CORS and Subresource Integrity
if (options.crossorigin != null || options.integrity) {
webpackConfig
.plugin('cors')
.use(require('../webpack/CorsPlugin'), [{
crossorigin: options.crossorigin,
integrity: options.integrity,
publicPath: options.publicPath
}])
}
// copy static assets in public/
const publicDir = api.resolve('public')
if (!isLegacyBundle && fs.existsSync(publicDir)) {
webpackConfig
.plugin('copy')
.use(require('copy-webpack-plugin'), [[{
from: publicDir,
to: outputDir,
toType: 'dir',
ignore: publicCopyIgnore
}]])
}
})
}
+219
View File
@@ -0,0 +1,219 @@
module.exports = (api, options) => {
api.chainWebpack(webpackConfig => {
const isLegacyBundle = process.env.VUE_CLI_MODERN_MODE && !process.env.VUE_CLI_MODERN_BUILD
const resolveLocal = require('../util/resolveLocal')
const getAssetPath = require('../util/getAssetPath')
const inlineLimit = 4096
const genAssetSubPath = dir => {
return getAssetPath(
options,
`${dir}/[name]${options.filenameHashing ? '.[hash:8]' : ''}.[ext]`
)
}
const genUrlLoaderOptions = dir => {
return {
limit: inlineLimit,
// use explicit fallback to avoid regression in url-loader>=1.1.0
fallback: {
loader: require.resolve('file-loader'),
options: {
name: genAssetSubPath(dir)
}
}
}
}
webpackConfig
.mode('development')
.context(api.service.context)
.entry('app')
.add('./src/main.js')
.end()
.output
.path(api.resolve(options.outputDir))
.filename(isLegacyBundle ? '[name]-legacy.js' : '[name].js')
.publicPath(options.publicPath)
webpackConfig.resolve
// This plugin can be removed once we switch to Webpack 6
.plugin('pnp')
.use({ ...require('pnp-webpack-plugin') })
.end()
.extensions
.merge(['.mjs', '.js', '.jsx', '.vue', '.json', '.wasm'])
.end()
.modules
.add('node_modules')
.add(api.resolve('node_modules'))
.add(resolveLocal('node_modules'))
.end()
.alias
.set('@', api.resolve('src'))
.set(
'vue$',
options.runtimeCompiler
? 'vue/dist/vue.esm.js'
: 'vue/dist/vue.runtime.esm.js'
)
webpackConfig.resolveLoader
.plugin('pnp-loaders')
.use({ ...require('pnp-webpack-plugin').topLevelLoader })
.end()
.modules
.add('node_modules')
.add(api.resolve('node_modules'))
.add(resolveLocal('node_modules'))
webpackConfig.module
.noParse(/^(vue|vue-router|vuex|vuex-router-sync)$/)
// js is handled by cli-plugin-babel ---------------------------------------
// vue-loader --------------------------------------------------------------
const vueLoaderCacheIdentifier = {
'vue-loader': require('vue-loader/package.json').version
}
// The following 2 deps are sure to exist in Vue 2 projects.
// But once we switch to Vue 3, they're no longer mandatory.
// (In Vue 3 they are replaced by @vue/compiler-sfc)
// So wrap them in a try catch block.
try {
vueLoaderCacheIdentifier['@vue/component-compiler-utils'] =
require('@vue/component-compiler-utils/package.json').version
vueLoaderCacheIdentifier['vue-template-compiler'] =
require('vue-template-compiler/package.json').version
} catch (e) {}
const vueLoaderCacheConfig = api.genCacheConfig('vue-loader', vueLoaderCacheIdentifier)
webpackConfig.module
.rule('vue')
.test(/\.vue$/)
.use('cache-loader')
.loader(require.resolve('cache-loader'))
.options(vueLoaderCacheConfig)
.end()
.use('vue-loader')
.loader(require.resolve('vue-loader'))
.options(Object.assign({
compilerOptions: {
whitespace: 'condense'
}
}, vueLoaderCacheConfig))
webpackConfig
.plugin('vue-loader')
.use(require('vue-loader/lib/plugin'))
// static assets -----------------------------------------------------------
webpackConfig.module
.rule('images')
.test(/\.(png|jpe?g|gif|webp)(\?.*)?$/)
.use('url-loader')
.loader(require.resolve('url-loader'))
.options(genUrlLoaderOptions('img'))
// do not base64-inline SVGs.
// https://github.com/facebookincubator/create-react-app/pull/1180
webpackConfig.module
.rule('svg')
.test(/\.(svg)(\?.*)?$/)
.use('file-loader')
.loader(require.resolve('file-loader'))
.options({
name: genAssetSubPath('img')
})
webpackConfig.module
.rule('media')
.test(/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/)
.use('url-loader')
.loader(require.resolve('url-loader'))
.options(genUrlLoaderOptions('media'))
webpackConfig.module
.rule('fonts')
.test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/i)
.use('url-loader')
.loader(require.resolve('url-loader'))
.options(genUrlLoaderOptions('fonts'))
// Other common pre-processors ---------------------------------------------
const maybeResolve = name => {
try {
return require.resolve(name)
} catch (error) {
return name
}
}
webpackConfig.module
.rule('pug')
.test(/\.pug$/)
.oneOf('pug-vue')
.resourceQuery(/vue/)
.use('pug-plain-loader')
.loader(maybeResolve('pug-plain-loader'))
.end()
.end()
.oneOf('pug-template')
.use('raw')
.loader(maybeResolve('raw-loader'))
.end()
.use('pug-plain-loader')
.loader(maybeResolve('pug-plain-loader'))
.end()
.end()
// shims
webpackConfig.node
.merge({
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// process is injected via DefinePlugin, although some 3rd party
// libraries may require a mock to work properly (#934)
process: 'mock',
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
})
const resolveClientEnv = require('../util/resolveClientEnv')
webpackConfig
.plugin('define')
.use(require('webpack').DefinePlugin, [
resolveClientEnv(options)
])
webpackConfig
.plugin('case-sensitive-paths')
.use(require('case-sensitive-paths-webpack-plugin'))
// friendly error plugin displays very confusing errors when webpack
// fails to resolve a loader, so we provide custom handlers to improve it
const { transformer, formatter } = require('../util/resolveLoaderError')
webpackConfig
.plugin('friendly-errors')
.use(require('@soda/friendly-errors-webpack-plugin'), [{
additionalTransformers: [transformer],
additionalFormatters: [formatter]
}])
const TerserPlugin = require('terser-webpack-plugin')
const terserOptions = require('./terserOptions')
webpackConfig.optimization
.minimizer('terser')
.use(TerserPlugin, [terserOptions(options)])
})
}
+253
View File
@@ -0,0 +1,253 @@
const fs = require('fs')
const path = require('path')
const { semver, warn, pauseSpinner, resumeSpinner } = require('@vue/cli-shared-utils')
const findExisting = (context, files) => {
for (const file of files) {
if (fs.existsSync(path.join(context, file))) {
return file
}
}
}
module.exports = (api, rootOptions) => {
api.chainWebpack(webpackConfig => {
const getAssetPath = require('../util/getAssetPath')
const shadowMode = !!process.env.VUE_CLI_CSS_SHADOW_MODE
const isProd = process.env.NODE_ENV === 'production'
let sassLoaderVersion
try {
sassLoaderVersion = semver.major(require('sass-loader/package.json').version)
} catch (e) {}
if (sassLoaderVersion < 8) {
pauseSpinner()
warn('A new version of sass-loader is available. Please upgrade for best experience.')
resumeSpinner()
}
const defaultSassLoaderOptions = {}
try {
defaultSassLoaderOptions.implementation = require('sass')
// since sass-loader 8, fibers will be automatically detected and used
if (sassLoaderVersion < 8) {
defaultSassLoaderOptions.fiber = require('fibers')
}
} catch (e) {}
const {
extract = isProd,
sourceMap = false,
loaderOptions = {}
} = rootOptions.css || {}
let { requireModuleExtension } = rootOptions.css || {}
if (typeof requireModuleExtension === 'undefined') {
if (loaderOptions.css && loaderOptions.css.modules) {
throw new Error('`css.requireModuleExtension` is required when custom css modules options provided')
}
requireModuleExtension = true
}
const shouldExtract = extract !== false && !shadowMode
const filename = getAssetPath(
rootOptions,
`css/[name]${rootOptions.filenameHashing ? '.[contenthash:8]' : ''}.css`
)
const extractOptions = Object.assign({
filename,
chunkFilename: filename
}, extract && typeof extract === 'object' ? extract : {})
// use relative publicPath in extracted CSS based on extract location
const cssPublicPath = process.env.VUE_CLI_BUILD_TARGET === 'lib'
// in lib mode, CSS is extracted to dist root.
? './'
: '../'.repeat(
extractOptions.filename
.replace(/^\.[\/\\]/, '')
.split(/[\/\\]/g)
.length - 1
)
// check if the project has a valid postcss config
// if it doesn't, don't use postcss-loader for direct style imports
// because otherwise it would throw error when attempting to load postcss config
const hasPostCSSConfig = !!(loaderOptions.postcss || api.service.pkg.postcss || findExisting(api.resolve('.'), [
'.postcssrc',
'.postcssrc.js',
'postcss.config.js',
'.postcssrc.yaml',
'.postcssrc.json'
]))
if (!hasPostCSSConfig) {
loaderOptions.postcss = {
plugins: [
require('autoprefixer')
]
}
}
// if building for production but not extracting CSS, we need to minimize
// the embbeded inline CSS as they will not be going through the optimizing
// plugin.
const needInlineMinification = isProd && !shouldExtract
const cssnanoOptions = {
preset: ['default', {
mergeLonghand: false,
cssDeclarationSorter: false
}]
}
if (rootOptions.productionSourceMap && sourceMap) {
cssnanoOptions.map = { inline: false }
}
function createCSSRule (lang, test, loader, options) {
const baseRule = webpackConfig.module.rule(lang).test(test)
// rules for <style lang="module">
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 extModulesRule = baseRule.oneOf('normal-modules').test(/\.module\.\w+$/)
applyLoaders(extModulesRule, true)
// rules for normal CSS imports
const normalRule = baseRule.oneOf('normal')
applyLoaders(normalRule, !requireModuleExtension)
function applyLoaders (rule, isCssModule) {
if (shouldExtract) {
rule
.use('extract-css-loader')
.loader(require('mini-css-extract-plugin').loader)
.options({
hmr: !isProd,
publicPath: cssPublicPath
})
} else {
rule
.use('vue-style-loader')
.loader(require.resolve('vue-style-loader'))
.options({
sourceMap,
shadowMode
})
}
const cssLoaderOptions = Object.assign({
sourceMap,
importLoaders: (
1 + // stylePostLoader injected by vue-loader
1 + // postcss-loader
(needInlineMinification ? 1 : 0)
)
}, loaderOptions.css)
if (isCssModule) {
cssLoaderOptions.modules = {
localIdentName: '[name]_[local]_[hash:base64:5]',
...cssLoaderOptions.modules
}
} else {
delete cssLoaderOptions.modules
}
rule
.use('css-loader')
.loader(require.resolve('css-loader'))
.options(cssLoaderOptions)
if (needInlineMinification) {
rule
.use('cssnano')
.loader(require.resolve('postcss-loader'))
.options({
sourceMap,
plugins: [require('cssnano')(cssnanoOptions)]
})
}
rule
.use('postcss-loader')
.loader(require.resolve('postcss-loader'))
.options(Object.assign({ sourceMap }, loaderOptions.postcss))
if (loader) {
let resolvedLoader
try {
resolvedLoader = require.resolve(loader)
} catch (error) {
resolvedLoader = loader
}
rule
.use(loader)
.loader(resolvedLoader)
.options(Object.assign({ sourceMap }, options))
}
}
}
createCSSRule('css', /\.css$/)
createCSSRule('postcss', /\.p(ost)?css$/)
createCSSRule('scss', /\.scss$/, 'sass-loader', Object.assign(
{},
defaultSassLoaderOptions,
loaderOptions.scss || loaderOptions.sass
))
if (sassLoaderVersion < 8) {
createCSSRule('sass', /\.sass$/, 'sass-loader', Object.assign(
{},
defaultSassLoaderOptions,
{
indentedSyntax: true
},
loaderOptions.sass
))
} else {
createCSSRule('sass', /\.sass$/, 'sass-loader', Object.assign(
{},
defaultSassLoaderOptions,
loaderOptions.sass,
{
sassOptions: Object.assign(
{},
loaderOptions.sass && loaderOptions.sass.sassOptions,
{
indentedSyntax: true
}
)
}
))
}
createCSSRule('less', /\.less$/, 'less-loader', loaderOptions.less)
createCSSRule('stylus', /\.styl(us)?$/, 'stylus-loader', Object.assign({
preferPathResolver: 'webpack'
}, loaderOptions.stylus))
// inject CSS extraction plugin
if (shouldExtract) {
webpackConfig
.plugin('extract-css')
.use(require('mini-css-extract-plugin'), [extractOptions])
// minify extracted CSS
if (isProd) {
webpackConfig
.plugin('optimize-css')
.use(require('@intervolga/optimize-cssnano-plugin'), [{
sourceMap: rootOptions.productionSourceMap && sourceMap,
cssnanoOptions
}])
}
}
})
}
+12
View 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>
+21
View File
@@ -0,0 +1,21 @@
module.exports = (api, options) => {
api.chainWebpack(webpackConfig => {
if (process.env.NODE_ENV === 'production') {
webpackConfig
.mode('production')
.devtool(options.productionSourceMap ? 'source-map' : false)
// keep module.id stable when vendor modules does not change
webpackConfig
.plugin('hash-module-ids')
.use(require('webpack/lib/HashedModuleIdsPlugin'), [{
hashDigest: 'hex'
}])
// disable optimization during tests to speed things up
if (process.env.VUE_CLI_TEST) {
webpackConfig.optimization.minimize(false)
}
}
})
}
+42
View File
@@ -0,0 +1,42 @@
module.exports = options => ({
terserOptions: {
compress: {
// turn off flags with small gains to speed up minification
arrows: false,
collapse_vars: false, // 0.3kb
comparisons: false,
computed_props: false,
hoist_funs: false,
hoist_props: false,
hoist_vars: false,
inline: false,
loops: false,
negate_iife: false,
properties: false,
reduce_funcs: false,
reduce_vars: false,
switches: false,
toplevel: false,
typeofs: false,
// a few flags with noticable gains/speed ratio
// numbers based on out of the box vendor bundle
booleans: true, // 0.7kb
if_return: true, // 0.4kb
sequences: true, // 0.7kb
unused: true, // 2.3kb
// required features to drop conditional branches
conditionals: true,
dead_code: true,
evaluate: true
},
mangle: {
safari10: true
}
},
sourceMap: options.productionSourceMap,
cache: true,
parallel: options.parallel,
extractComments: false
})
+143
View File
@@ -0,0 +1,143 @@
const { createSchema, validate } = require('@vue/cli-shared-utils')
const schema = createSchema(joi => joi.object({
publicPath: joi.string().allow(''),
outputDir: joi.string(),
assetsDir: joi.string().allow(''),
indexPath: joi.string(),
filenameHashing: joi.boolean(),
runtimeCompiler: joi.boolean(),
transpileDependencies: joi.array(),
productionSourceMap: joi.boolean(),
parallel: joi.alternatives().try([
joi.boolean(),
joi.number().integer()
]),
devServer: joi.object(),
pages: joi.object().pattern(
/\w+/,
joi.alternatives().try([
joi.string().required(),
joi.array().items(joi.string().required()),
joi.object().keys({
entry: joi.alternatives().try([
joi.string().required(),
joi.array().items(joi.string().required())
]).required()
}).unknown(true)
])
),
crossorigin: joi.string().valid(['', 'anonymous', 'use-credentials']),
integrity: joi.boolean(),
// css
css: joi.object({
// TODO: deprecate this after joi 16 release
modules: joi.boolean(),
requireModuleExtension: joi.boolean(),
extract: joi.alternatives().try(joi.boolean(), joi.object()),
sourceMap: joi.boolean(),
loaderOptions: joi.object({
css: joi.object(),
sass: joi.object(),
scss: joi.object(),
less: joi.object(),
stylus: joi.object(),
postcss: joi.object()
})
}),
// webpack
chainWebpack: joi.func(),
configureWebpack: joi.alternatives().try(
joi.object(),
joi.func()
),
// known runtime options for built-in plugins
lintOnSave: joi.any().valid([true, false, 'error', 'warning', 'default']),
pwa: joi.object(),
// 3rd party plugin options
pluginOptions: joi.object()
}))
exports.validate = (options, cb) => {
validate(options, schema, cb)
}
// #2110
// https://github.com/nodejs/node/issues/19022
// in some cases cpus() returns undefined, and may simply throw in the future
function hasMultipleCores () {
try {
return require('os').cpus().length > 1
} catch (e) {
return false
}
}
exports.defaults = () => ({
// project deployment base
publicPath: '/',
// where to output built files
outputDir: 'dist',
// where to put static assets (js/css/img/font/...)
assetsDir: '',
// filename for index.html (relative to outputDir)
indexPath: 'index.html',
// whether filename will contain hash part
filenameHashing: true,
// boolean, use full build?
runtimeCompiler: false,
// deps to transpile
transpileDependencies: [
/* string or regex */
],
// sourceMap for production build?
productionSourceMap: !process.env.VUE_CLI_TEST,
// use thread-loader for babel & TS in production build
// enabled by default if the machine has more than 1 cores
parallel: hasMultipleCores(),
// multi-page config
pages: undefined,
// <script type="module" crossorigin="use-credentials">
// #1656, #1867, #2025
crossorigin: undefined,
// subresource integrity
integrity: false,
css: {
// extract: true,
// modules: false,
// sourceMap: false,
// loaderOptions: {}
},
// whether to use eslint-loader
lintOnSave: 'default',
devServer: {
/*
open: process.platform === 'darwin',
host: '0.0.0.0',
port: 8080,
https: false,
hotOnly: false,
proxy: null, // string | Object
before: app => {}
*/
}
})
+7
View File
@@ -0,0 +1,7 @@
const path = require('path')
module.exports = function getAssetPath (options, filePath) {
return options.assetsDir
? path.posix.join(options.assetsDir, filePath)
: filePath
}
+9
View File
@@ -0,0 +1,9 @@
module.exports = function getPadLength (obj) {
let longest = 10
for (const name in obj) {
if (name.length + 1 > longest) {
longest = name.length + 1
}
}
return longest
}
+4
View File
@@ -0,0 +1,4 @@
module.exports = function isAbsoluteUrl (url) {
// A URL is considered absolute if it begins with "<scheme>://" or "//"
return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url)
}
+205
View File
@@ -0,0 +1,205 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file at
* https://github.com/facebookincubator/create-react-app/blob/master/LICENSE
*/
const fs = require('fs')
const url = require('url')
const path = require('path')
const { chalk } = require('@vue/cli-shared-utils')
const address = require('address')
const defaultConfig = {
logLevel: 'silent',
secure: false,
changeOrigin: true,
ws: true,
xfwd: true
}
module.exports = function prepareProxy (proxy, appPublicFolder) {
// `proxy` lets you specify alternate servers for specific requests.
// It can either be a string or an object conforming to the Webpack dev server proxy configuration
// https://webpack.github.io/docs/webpack-dev-server.html
if (!proxy) {
return undefined
}
if (Array.isArray(proxy) || (typeof proxy !== 'object' && typeof proxy !== 'string')) {
console.log(
chalk.red(
'When specified, "proxy" in package.json must be a string or an object.'
)
)
console.log(
chalk.red('Instead, the type of "proxy" was "' + typeof proxy + '".')
)
console.log(
chalk.red(
'Either remove "proxy" from package.json, or make it an object.'
)
)
process.exit(1)
}
// If proxy is specified, let it handle any request except for
// files in the public folder and requests to the WebpackDevServer socket endpoint.
// https://github.com/facebook/create-react-app/issues/6720
function mayProxy (pathname) {
const maybePublicPath = path.resolve(appPublicFolder, pathname.slice(1))
const isPublicFileRequest = fs.existsSync(maybePublicPath)
const isWdsEndpointRequest = pathname.startsWith('/sockjs-node') // used by webpackHotDevClient
return !(isPublicFileRequest || isWdsEndpointRequest)
}
function createProxyEntry (target, usersOnProxyReq, context) {
// #2478
// There're a little-known use case that the `target` field is an object rather than a string
// https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/https.md
if (typeof target === 'string' && process.platform === 'win32') {
target = resolveLoopback(target)
}
return {
target,
context (pathname, req) {
// is a static asset
if (!mayProxy(pathname)) {
return false
}
if (context) {
// Explicit context, e.g. /api
return pathname.match(context)
} else {
// not a static request
if (req.method !== 'GET') {
return true
}
// Heuristics: if request `accept`s text/html, we pick /index.html.
// Modern browsers include text/html into `accept` header when navigating.
// However API calls like `fetch()` won’t generally accept text/html.
// If this heuristic doesn’t work well for you, use a custom `proxy` object.
return (
req.headers.accept &&
req.headers.accept.indexOf('text/html') === -1
)
}
},
onProxyReq (proxyReq, req, res) {
if (usersOnProxyReq) {
usersOnProxyReq(proxyReq, req, res)
}
// Browsers may send Origin headers even with same-origin
// requests. To prevent CORS issues, we have to change
// the Origin to match the target URL.
if (!proxyReq.agent && proxyReq.getHeader('origin')) {
proxyReq.setHeader('origin', target)
}
},
onError: onProxyError(target)
}
}
// Support proxy as a string for those who are using the simple proxy option
if (typeof proxy === 'string') {
if (!/^http(s)?:\/\//.test(proxy)) {
console.log(
chalk.red(
'When "proxy" is specified in package.json it must start with either http:// or https://'
)
)
process.exit(1)
}
return [
Object.assign({}, defaultConfig, createProxyEntry(proxy))
]
}
// Otherwise, proxy is an object so create an array of proxies to pass to webpackDevServer
return Object.keys(proxy).map(context => {
const config = proxy[context]
if (!config.hasOwnProperty('target')) {
console.log(
chalk.red(
'When `proxy` in package.json is an object, each `context` object must have a ' +
'`target` property specified as a url string'
)
)
process.exit(1)
}
const entry = createProxyEntry(config.target, config.onProxyReq, context)
return Object.assign({}, defaultConfig, config, entry)
})
}
function resolveLoopback (proxy) {
const o = url.parse(proxy)
o.host = undefined
if (o.hostname !== 'localhost') {
return proxy
}
// Unfortunately, many languages (unlike node) do not yet support IPv6.
// This means even though localhost resolves to ::1, the application
// must fall back to IPv4 (on 127.0.0.1).
// We can re-enable this in a few years.
/* try {
o.hostname = address.ipv6() ? '::1' : '127.0.0.1';
} catch (_ignored) {
o.hostname = '127.0.0.1';
}*/
try {
// Check if we're on a network; if we are, chances are we can resolve
// localhost. Otherwise, we can just be safe and assume localhost is
// IPv4 for maximum compatibility.
if (!address.ip()) {
o.hostname = '127.0.0.1'
}
} catch (_ignored) {
o.hostname = '127.0.0.1'
}
return url.format(o)
}
// We need to provide a custom onError function for httpProxyMiddleware.
// It allows us to log custom error messages on the console.
function onProxyError (proxy) {
return (err, req, res) => {
const host = req.headers && req.headers.host
console.log(
chalk.red('Proxy error:') +
' Could not proxy request ' +
chalk.cyan(req.url) +
' from ' +
chalk.cyan(host) +
' to ' +
chalk.cyan(proxy) +
'.'
)
console.log(
'See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (' +
chalk.cyan(err.code) +
').'
)
console.log()
// And immediately send the proper error response to the client.
// Otherwise, the request will eventually timeout with ERR_EMPTY_RESPONSE on the client side.
if (res.writeHead && !res.headersSent) {
res.writeHead(500)
}
res.end(
'Proxy error: Could not proxy request ' +
req.url +
' from ' +
host +
' to ' +
proxy +
' (' +
err.code +
').'
)
}
}
+70
View File
@@ -0,0 +1,70 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file at
* https://github.com/facebookincubator/create-react-app/blob/master/LICENSE
*/
const url = require('url')
const { chalk } = require('@vue/cli-shared-utils')
const address = require('address')
const defaultGateway = require('default-gateway')
module.exports = function prepareUrls (protocol, host, port, pathname = '/') {
const formatUrl = hostname =>
url.format({
protocol,
hostname,
port,
pathname
})
const prettyPrintUrl = hostname =>
url.format({
protocol,
hostname,
port: chalk.bold(port),
pathname
})
const isUnspecifiedHost = host === '0.0.0.0' || host === '::'
let prettyHost, lanUrlForConfig
let lanUrlForTerminal = chalk.gray('unavailable')
if (isUnspecifiedHost) {
prettyHost = 'localhost'
try {
// This can only return an IPv4 address
const result = defaultGateway.v4.sync()
lanUrlForConfig = address.ip(result && result.interface)
if (lanUrlForConfig) {
// Check if the address is a private ip
// https://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
if (
/^10[.]|^172[.](1[6-9]|2[0-9]|3[0-1])[.]|^192[.]168[.]/.test(
lanUrlForConfig
)
) {
// Address is private, format it for later use
lanUrlForTerminal = prettyPrintUrl(lanUrlForConfig)
} else {
// Address is not private, so we will discard it
lanUrlForConfig = undefined
}
}
} catch (_e) {
// ignored
}
} else {
prettyHost = host
lanUrlForConfig = host
lanUrlForTerminal = prettyPrintUrl(lanUrlForConfig)
}
const localUrlForTerminal = prettyPrintUrl(prettyHost)
const localUrlForBrowser = formatUrl(prettyHost)
return {
lanUrlForConfig,
lanUrlForTerminal,
localUrlForTerminal,
localUrlForBrowser
}
}
+22
View File
@@ -0,0 +1,22 @@
const prefixRE = /^VUE_APP_/
module.exports = function resolveClientEnv (options, raw) {
const env = {}
Object.keys(process.env).forEach(key => {
if (prefixRE.test(key) || key === 'NODE_ENV') {
env[key] = process.env[key]
}
})
env.BASE_URL = options.publicPath
if (raw) {
return env
}
for (const key in env) {
env[key] = JSON.stringify(env[key])
}
return {
'process.env': env
}
}
+47
View File
@@ -0,0 +1,47 @@
const { chalk } = require('@vue/cli-shared-utils')
const rules = [
{
type: 'cant-resolve-loader',
re: /Can't resolve '(.*loader)'/,
msg: (e, match) => (
`Failed to resolve loader: ${chalk.yellow(match[1])}\n` +
`You may need to install it.`
)
}
]
exports.transformer = error => {
if (error.webpackError) {
const message = typeof error.webpackError === 'string'
? error.webpackError
: error.webpackError.message || ''
for (const { re, msg, type } of rules) {
const match = message.match(re)
if (match) {
return Object.assign({}, error, {
// type is necessary to avoid being printed as defualt error
// by friendly-error-webpack-plugin
type,
shortMessage: msg(error, match)
})
}
}
// no match, unknown webpack error without a message.
// friendly-error-webpack-plugin fails to handle this.
if (!error.message) {
return Object.assign({}, error, {
type: 'unknown-webpack-error',
shortMessage: message
})
}
}
return error
}
exports.formatter = errors => {
errors = errors.filter(e => e.shortMessage)
if (errors.length) {
return errors.map(e => e.shortMessage)
}
}
+5
View File
@@ -0,0 +1,5 @@
const path = require('path')
module.exports = function resolveLocal (...args) {
return path.join(__dirname, '../../', ...args)
}
@@ -0,0 +1,38 @@
module.exports = function validateWebpackConfig (
webpackConfig,
api,
options,
target = 'app'
) {
const singleConfig = Array.isArray(webpackConfig)
? webpackConfig[0]
: webpackConfig
const actualTargetDir = singleConfig.output.path
if (actualTargetDir !== api.resolve(options.outputDir)) {
// user directly modifies output.path in configureWebpack or chainWebpack.
// this is not supported because there's no way for us to give copy
// plugin the correct value this way.
throw new Error(
`\n\nConfiguration Error: ` +
`Avoid modifying webpack output.path directly. ` +
`Use the "outputDir" option instead.\n`
)
}
if (actualTargetDir === api.service.context) {
throw new Error(
`\n\nConfiguration Error: ` +
`Do not set output directory to project root.\n`
)
}
if (target === 'app' && singleConfig.output.publicPath !== options.publicPath) {
throw new Error(
`\n\nConfiguration Error: ` +
`Avoid modifying webpack output.publicPath directly. ` +
`Use the "publicPath" option instead.\n`
)
}
}
+67
View File
@@ -0,0 +1,67 @@
module.exports = class CorsPlugin {
constructor ({ publicPath, crossorigin, integrity }) {
this.crossorigin = crossorigin
this.integrity = integrity
this.publicPath = publicPath
}
apply (compiler) {
const ID = `vue-cli-cors-plugin`
compiler.hooks.compilation.tap(ID, compilation => {
const ssri = require('ssri')
const computeHash = url => {
const filename = url.replace(this.publicPath, '')
const asset = compilation.assets[filename]
if (asset) {
const src = asset.source()
const integrity = ssri.fromData(src, {
algorithms: ['sha384']
})
return integrity.toString()
}
}
compilation.hooks.htmlWebpackPluginAlterAssetTags.tap(ID, data => {
const tags = [...data.head, ...data.body]
if (this.crossorigin != null) {
tags.forEach(tag => {
if (tag.tagName === 'script' || tag.tagName === 'link') {
tag.attributes.crossorigin = this.crossorigin
}
})
}
if (this.integrity) {
tags.forEach(tag => {
if (tag.tagName === 'script') {
const hash = computeHash(tag.attributes.src)
if (hash) {
tag.attributes.integrity = hash
}
} else if (tag.tagName === 'link' && tag.attributes.rel === 'stylesheet') {
const hash = computeHash(tag.attributes.href)
if (hash) {
tag.attributes.integrity = hash
}
}
})
// when using SRI, Chrome somehow cannot reuse
// the preloaded resource, and causes the files to be downloaded twice.
// this is a Chrome bug (https://bugs.chromium.org/p/chromium/issues/detail?id=677022)
// for now we disable preload if SRI is used.
data.head = data.head.filter(tag => {
return !(
tag.tagName === 'link' &&
tag.attributes.rel === 'preload'
)
})
}
})
compilation.hooks.htmlWebpackPluginAfterHtmlProcessing.tap(ID, data => {
data.html = data.html.replace(/\scrossorigin=""/g, ' crossorigin')
})
})
}
}
+204
View File
@@ -0,0 +1,204 @@
// From https://github.com/FormidableLabs/webpack-dashboard/blob/7f99b31c5f00a7818d8129cb8a8fc6eb1b71799c/plugin/index.js
// Modified by Guillaume Chau (Akryum)
/* eslint-disable max-params, max-statements */
'use strict'
const path = require('path')
const fs = require('fs-extra')
const webpack = require('webpack')
const { IpcMessenger } = require('@vue/cli-shared-utils')
const { analyzeBundle } = require('./analyzeBundle')
const ID = 'vue-cli-dashboard-plugin'
const ONE_SECOND = 1000
const FILENAME_QUERY_REGEXP = /\?.*$/
const ipc = new IpcMessenger()
function getTimeMessage (timer) {
let time = Date.now() - timer
if (time >= ONE_SECOND) {
time /= ONE_SECOND
time = Math.round(time)
time += 's'
} else {
time += 'ms'
}
return ` (${time})`
}
class DashboardPlugin {
constructor (options) {
this.type = options.type
if (this.type === 'build' && options.modernBuild) {
this.type = 'build-modern'
}
this.watching = false
this.autoDisconnect = !options.keepAlive
}
cleanup () {
this.sendData = null
if (this.autoDisconnect) ipc.disconnect()
}
apply (compiler) {
let sendData = this.sendData
let timer
let assetSources = new Map()
if (!sendData) {
sendData = data => ipc.send({
webpackDashboardData: {
type: this.type,
value: data
}
})
}
// Progress status
let progressTime = Date.now()
const progressPlugin = new webpack.ProgressPlugin((percent, msg) => {
// Debouncing
const time = Date.now()
if (time - progressTime > 300) {
progressTime = time
sendData([
{
type: 'status',
value: 'Compiling'
},
{
type: 'progress',
value: percent
},
{
type: 'operations',
value: msg + getTimeMessage(timer)
}
])
}
})
progressPlugin.apply(compiler)
compiler.hooks.watchRun.tap(ID, c => {
this.watching = true
})
compiler.hooks.run.tap(ID, c => {
this.watching = false
})
compiler.hooks.compile.tap(ID, () => {
timer = Date.now()
sendData([
{
type: 'status',
value: 'Compiling'
},
{
type: 'progress',
value: 0
}
])
})
compiler.hooks.invalid.tap(ID, () => {
sendData([
{
type: 'status',
value: 'Invalidated'
},
{
type: 'progress',
value: 0
},
{
type: 'operations',
value: 'idle'
}
])
})
compiler.hooks.failed.tap(ID, () => {
sendData([
{
type: 'status',
value: 'Failed'
},
{
type: 'operations',
value: `idle${getTimeMessage(timer)}`
}
])
})
compiler.hooks.afterEmit.tap(ID, compilation => {
assetSources = new Map()
for (const name in compilation.assets) {
const asset = compilation.assets[name]
assetSources.set(name.replace(FILENAME_QUERY_REGEXP, ''), asset.source())
}
})
compiler.hooks.done.tap(ID, stats => {
let statsData = stats.toJson()
// Sometimes all the information is located in `children` array
if ((!statsData.assets || !statsData.assets.length) && statsData.children && statsData.children.length) {
statsData = statsData.children[0]
}
const outputPath = compiler.options.output.path
statsData.assets.forEach(asset => {
// Removing query part from filename (yes, somebody uses it for some reason and Webpack supports it)
asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, '')
asset.fullPath = path.join(outputPath, asset.name)
})
// Analyze the assets and update sizes on assets and modules
analyzeBundle(statsData, assetSources)
const hasErrors = stats.hasErrors()
sendData([
{
type: 'status',
value: hasErrors ? 'Failed' : 'Success'
},
{
type: 'progress',
value: 1
},
{
type: 'operations',
value: `idle${getTimeMessage(timer)}`
}
])
const statsFile = path.resolve(process.cwd(), `./node_modules/.stats-${this.type}.json`)
fs.writeJson(statsFile, {
errors: hasErrors,
warnings: stats.hasWarnings(),
data: statsData
}).then(() => {
sendData([
{
type: 'stats'
}
])
if (!this.watching) {
this.cleanup()
}
}).catch(error => {
console.error(error)
})
})
}
}
module.exports = DashboardPlugin
+111
View File
@@ -0,0 +1,111 @@
const fs = require('fs-extra')
const path = require('path')
// 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()}}();`
class ModernModePlugin {
constructor ({ targetDir, isModernBuild, unsafeInline, jsDirectory }) {
this.targetDir = targetDir
this.isModernBuild = isModernBuild
this.unsafeInline = unsafeInline
this.jsDirectory = jsDirectory
}
apply (compiler) {
if (!this.isModernBuild) {
this.applyLegacy(compiler)
} else {
this.applyModern(compiler)
}
}
applyLegacy (compiler) {
const ID = `vue-cli-legacy-bundle`
compiler.hooks.compilation.tap(ID, compilation => {
compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(ID, async (data, cb) => {
// get stats, write to disk
await fs.ensureDir(this.targetDir)
const htmlName = path.basename(data.plugin.options.filename)
// Watch out for output files in sub directories
const htmlPath = path.dirname(data.plugin.options.filename)
const tempFilename = path.join(this.targetDir, htmlPath, `legacy-assets-${htmlName}.json`)
await fs.mkdirp(path.dirname(tempFilename))
await fs.writeFile(tempFilename, JSON.stringify(data.body))
cb()
})
})
}
applyModern (compiler) {
const ID = `vue-cli-modern-bundle`
compiler.hooks.compilation.tap(ID, compilation => {
compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(ID, async (data, cb) => {
// use <script type="module"> for modern assets
data.body.forEach(tag => {
if (tag.tagName === 'script' && tag.attributes) {
tag.attributes.type = 'module'
}
})
// use <link rel="modulepreload"> instead of <link rel="preload">
// for modern assets
data.head.forEach(tag => {
if (tag.tagName === 'link' &&
tag.attributes.rel === 'preload' &&
tag.attributes.as === 'script') {
tag.attributes.rel = 'modulepreload'
}
})
// inject links for legacy assets as <script nomodule>
const htmlName = path.basename(data.plugin.options.filename)
// Watch out for output files in sub directories
const htmlPath = path.dirname(data.plugin.options.filename)
const tempFilename = path.join(this.targetDir, htmlPath, `legacy-assets-${htmlName}.json`)
const legacyAssets = JSON.parse(await fs.readFile(tempFilename, 'utf-8'))
.filter(a => a.tagName === 'script' && a.attributes)
legacyAssets.forEach(a => { a.attributes.nomodule = '' })
if (this.unsafeInline) {
// inject inline Safari 10 nomodule fix
data.body.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 new Buffer(safariFix)
},
size: function () {
return Buffer.byteLength(safariFix)
}
}
data.body.push({
tagName: 'script',
closeTag: true,
attributes: {
src: fullSafariFixPath
}
})
}
data.body.push(...legacyAssets)
await fs.remove(tempFilename)
cb()
})
compilation.hooks.htmlWebpackPluginAfterHtmlProcessing.tap(ID, data => {
data.html = data.html.replace(/\snomodule="">/g, ' nomodule>')
})
})
}
}
ModernModePlugin.safariFix = safariFix
module.exports = ModernModePlugin
+16
View File
@@ -0,0 +1,16 @@
const fs = require('fs-extra')
module.exports = class MovePlugin {
constructor (from, to) {
this.from = from
this.to = to
}
apply (compiler) {
compiler.hooks.done.tap('move-plugin', () => {
if (fs.existsSync(this.from)) {
fs.moveSync(this.from, this.to, { overwrite: true })
}
})
}
}
+328
View File
@@ -0,0 +1,328 @@
// From https://github.com/webpack-contrib/webpack-bundle-analyzer/blob/ba3dbd71cec7becec0fbf529833204425f66efde/src/parseUtils.js
// Modified by Guillaume Chau (Akryum)
const acorn = require('acorn')
const walk = require('acorn-walk')
const mapValues = require('lodash.mapvalues')
const transform = require('lodash.transform')
const zlib = require('zlib')
const { warn } = require('@vue/cli-shared-utils')
exports.analyzeBundle = function analyzeBundle (bundleStats, assetSources) {
// Picking only `*.js` assets from bundle that has non-empty `chunks` array
const jsAssets = []
const otherAssets = []
// Separate JS assets
bundleStats.assets.forEach(asset => {
if (asset.name.endsWith('.js') && asset.chunks && asset.chunks.length) {
jsAssets.push(asset)
} else {
otherAssets.push(asset)
}
})
// Trying to parse bundle assets and get real module sizes
let bundlesSources = null
let parsedModules = null
bundlesSources = {}
parsedModules = {}
for (const asset of jsAssets) {
const source = assetSources.get(asset.name)
let bundleInfo
try {
bundleInfo = parseBundle(source)
} catch (err) {
bundleInfo = null
}
if (!bundleInfo) {
warn(
`\nCouldn't parse bundle asset "${asset.fullPath}".\n` +
'Analyzer will use module sizes from stats file.\n'
)
parsedModules = null
bundlesSources = null
break
}
bundlesSources[asset.name] = bundleInfo.src
Object.assign(parsedModules, bundleInfo.modules)
}
// Update sizes
bundleStats.modules.forEach(module => {
const parsedSrc = parsedModules && parsedModules[module.id]
module.size = {
stats: module.size
}
if (parsedSrc) {
module.size.parsed = parsedSrc.length
module.size.gzip = getGzipSize(parsedSrc)
} else {
module.size.parsed = module.size.stats
module.size.gzip = 0
}
})
jsAssets.forEach(asset => {
const src = bundlesSources && bundlesSources[asset.name]
asset.size = {
stats: asset.size
}
if (src) {
asset.size.parsed = src.length
asset.size.gzip = getGzipSize(src)
} else {
asset.size.parsed = asset.size.stats
asset.size.gzip = 0
}
}, {})
otherAssets.forEach(asset => {
const src = assetSources.get(asset.name)
asset.size = {
stats: asset.size,
parsed: asset.size
}
if (src) {
asset.size.gzip = getGzipSize(src)
} else {
asset.size.gzip = 0
}
})
}
function parseBundle (bundleContent) {
const ast = acorn.parse(bundleContent, {
sourceType: 'script',
// I believe in a bright future of ECMAScript!
// Actually, it's set to `2050` to support the latest ECMAScript version that currently exists.
// Seems like `acorn` supports such weird option value.
ecmaVersion: 2050
})
const walkState = {
locations: null
}
walk.recursive(
ast,
walkState,
{
CallExpression (node, state, c) {
if (state.sizes) return
const args = node.arguments
// Additional bundle without webpack loader.
// Modules are stored in second argument, after chunk ids:
// webpackJsonp([<chunks>], <modules>, ...)
// As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
if (
node.callee.type === 'Identifier' &&
args.length >= 2 &&
isArgumentContainsChunkIds(args[0]) &&
isArgumentContainsModulesList(args[1])
) {
state.locations = getModulesLocationFromFunctionArgument(args[1])
return
}
// Additional bundle without webpack loader, with module IDs optimized.
// Modules are stored in second arguments Array(n).concat() call
// webpackJsonp([<chunks>], Array([minimum ID]).concat([<module>, <module>, ...]))
// As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
if (
node.callee.type === 'Identifier' &&
(args.length === 2 || args.length === 3) &&
isArgumentContainsChunkIds(args[0]) &&
isArgumentArrayConcatContainingChunks(args[1])
) {
state.locations = getModulesLocationFromArrayConcat(args[1])
return
}
// Main bundle with webpack loader
// Modules are stored in first argument:
// (function (...) {...})(<modules>)
if (
node.callee.type === 'FunctionExpression' &&
!node.callee.id &&
args.length === 1 &&
isArgumentContainsModulesList(args[0])
) {
state.locations = getModulesLocationFromFunctionArgument(args[0])
return
}
// Additional bundles with webpack 4 are loaded with:
// (window.webpackJsonp=window.webpackJsonp||[]).push([[chunkId], [<module>, <module>], [[optional_entries]]]);
if (
isWindowPropertyPushExpression(node) &&
args.length === 1 &&
isArgumentContainingChunkIdsAndModulesList(args[0])
) {
state.locations = getModulesLocationFromFunctionArgument(args[0].elements[1])
return
}
// Walking into arguments because some of plugins (e.g. `DedupePlugin`) or some Webpack
// features (e.g. `umd` library output) can wrap modules list into additional IIFE.
args.forEach(arg => c(arg, state))
}
}
)
if (!walkState.locations) {
return null
}
return {
src: bundleContent,
modules: mapValues(walkState.locations,
loc => bundleContent.slice(loc.start, loc.end)
)
}
}
function getGzipSize (buffer) {
return zlib.gzipSync(buffer).length
}
function isArgumentContainsChunkIds (arg) {
// Array of numeric or string ids. Chunk IDs are strings when NamedChunksPlugin is used
return (arg.type === 'ArrayExpression' && arg.elements.every(isModuleId))
}
function isArgumentContainsModulesList (arg) {
if (arg.type === 'ObjectExpression') {
return arg.properties
.map(prop => prop.value)
.every(isModuleWrapper)
}
if (arg.type === 'ArrayExpression') {
// Modules are contained in array.
// Array indexes are module ids
return arg.elements.every(elem =>
// Some of array items may be skipped because there is no module with such id
!elem ||
isModuleWrapper(elem)
)
}
return false
}
function isArgumentContainingChunkIdsAndModulesList (arg) {
if (
arg.type === 'ArrayExpression' &&
arg.elements.length >= 2 &&
isArgumentContainsChunkIds(arg.elements[0]) &&
isArgumentContainsModulesList(arg.elements[1])
) {
return true
}
return false
}
function isArgumentArrayConcatContainingChunks (arg) {
if (
arg.type === 'CallExpression' &&
arg.callee.type === 'MemberExpression' &&
// Make sure the object called is `Array(<some number>)`
arg.callee.object.type === 'CallExpression' &&
arg.callee.object.callee.type === 'Identifier' &&
arg.callee.object.callee.name === 'Array' &&
arg.callee.object.arguments.length === 1 &&
isNumericId(arg.callee.object.arguments[0]) &&
// Make sure the property X called for `Array(<some number>).X` is `concat`
arg.callee.property.type === 'Identifier' &&
arg.callee.property.name === 'concat' &&
// Make sure exactly one array is passed in to `concat`
arg.arguments.length === 1 &&
arg.arguments[0].type === 'ArrayExpression'
) {
// Modules are contained in `Array(<minimum ID>).concat(` array:
// https://github.com/webpack/webpack/blob/v1.14.0/lib/Template.js#L91
// The `<minimum ID>` + array indexes are module ids
return true
}
return false
}
function isWindowPropertyPushExpression (node) {
return node.callee.type === 'MemberExpression' &&
node.callee.property.name === 'push' &&
node.callee.object.type === 'AssignmentExpression' &&
node.callee.object.left.object.name === 'window'
}
function isModuleWrapper (node) {
return (
// It's an anonymous function expression that wraps module
((node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') && !node.id) ||
// If `DedupePlugin` is used it can be an ID of duplicated module...
isModuleId(node) ||
// or an array of shape [<module_id>, ...args]
(node.type === 'ArrayExpression' && node.elements.length > 1 && isModuleId(node.elements[0]))
)
}
function isModuleId (node) {
return (node.type === 'Literal' && (isNumericId(node) || typeof node.value === 'string'))
}
function isNumericId (node) {
return (node.type === 'Literal' && Number.isInteger(node.value) && node.value >= 0)
}
function getModulesLocationFromFunctionArgument (arg) {
if (arg.type === 'ObjectExpression') {
const modulesNodes = arg.properties
return transform(modulesNodes, (result, moduleNode) => {
const moduleId = moduleNode.key.name || moduleNode.key.value
result[moduleId] = getModuleLocation(moduleNode.value)
}, {})
}
if (arg.type === 'ArrayExpression') {
const modulesNodes = arg.elements
return transform(modulesNodes, (result, moduleNode, i) => {
if (!moduleNode) return
result[i] = getModuleLocation(moduleNode)
}, {})
}
return {}
}
function getModulesLocationFromArrayConcat (arg) {
// arg(CallExpression) =
// Array([minId]).concat([<minId module>, <minId+1 module>, ...])
//
// Get the [minId] value from the Array() call first argument literal value
const minId = arg.callee.object.arguments[0].value
// The modules reside in the `concat()` function call arguments
const modulesNodes = arg.arguments[0].elements
return transform(modulesNodes, (result, moduleNode, i) => {
if (!moduleNode) return
result[i + minId] = getModuleLocation(moduleNode)
}, {})
}
function getModuleLocation (node) {
return { start: node.start, end: node.end }
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../json5/lib/cli.js" "$@"
else
exec node "$basedir/../json5/lib/cli.js" "$@"
fi
+17
View File
@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\json5\lib\cli.js" %*
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../json5/lib/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../json5/lib/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../json5/lib/cli.js" $args
} else {
& "node$exe" "$basedir/../json5/lib/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret
+23
View File
@@ -0,0 +1,23 @@
The MIT Expat Licence.
Copyright (c) 2012 Michael Mclaughlin
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+263
View File
@@ -0,0 +1,263 @@
# big.js #
A small, fast JavaScript library for arbitrary-precision decimal arithmetic.
The little sister to [bignumber.js](https://github.com/MikeMcl/bignumber.js/).
See also [decimal.js](https://github.com/MikeMcl/decimal.js/), and [here](https://github.com/MikeMcl/big.js/wiki) for the difference between them.
## Features
- Faster, smaller and easier-to-use than JavaScript versions of Java's BigDecimal
- Only 2.7 KB minified and gzipped
- Simple API
- Replicates the `toExponential`, `toFixed` and `toPrecision` methods of JavaScript's Number type
- Includes a `sqrt` method
- Stores values in an accessible decimal floating point format
- No dependencies
- Comprehensive [documentation](http://mikemcl.github.io/big.js/) and test set
## Load
The library is the single JavaScript file *big.js* (or *big.min.js*, which is *big.js* minified).
It can be loaded via a script tag in an HTML document for the browser
<script src='./relative/path/to/big.js'></script>
or as a CommonJS, [Node.js](http://nodejs.org) or AMD module using `require`.
var Big = require('big.js');
For Node.js, the library is available from the npm registry:
$ npm install big.js
## Use
*In all examples below, `var`, semicolons and `toString` calls are not shown.
If a commented-out value is in quotes it means `toString` has been called on the preceding expression.*
The library exports a single function: Big, the constructor of Big number instances.
It accepts a value of type Number, String or Big number Object.
x = new Big(123.4567)
y = Big('123456.7e-3') // 'new' is optional
z = new Big(x)
x.eq(y) && x.eq(z) && y.eq(z) // true
A Big number is immutable in the sense that it is not changed by its methods.
0.3 - 0.1 // 0.19999999999999998
x = new Big(0.3)
x.minus(0.1) // "0.2"
x // "0.3"
The methods that return a Big number can be chained.
x.div(y).plus(z).times(9).minus('1.234567801234567e+8').plus(976.54321).div('2598.11772')
x.sqrt().div(y).pow(3).gt(y.mod(z)) // true
Like JavaScript's Number type, there are `toExponential`, `toFixed` and `toPrecision` methods.
x = new Big(255.5)
x.toExponential(5) // "2.55500e+2"
x.toFixed(5) // "255.50000"
x.toPrecision(5) // "255.50"
The maximum number of decimal places and the rounding mode used to round the results of the `div`, `sqrt` and `pow`
(with negative exponent) methods is determined by the value of the `DP` and `RM` properties of the `Big` number constructor.
The other methods always give the exact result.
(From *v3.0.0*, multiple Big number constructors can be created, see Change Log below.)
Big.DP = 10
Big.RM = 1
x = new Big(2);
y = new Big(3);
z = x.div(y) // "0.6666666667"
z.sqrt() // "0.8164965809"
z.pow(-3) // "3.3749999995"
z.times(z) // "0.44444444448888888889"
z.times(z).round(10) // "0.4444444445"
The value of a Big number is stored in a decimal floating point format in terms of a coefficient, exponent and sign.
x = new Big(-123.456);
x.c // [1,2,3,4,5,6] coefficient (i.e. significand)
x.e // 2 exponent
x.s // -1 sign
For further information see the [API](http://mikemcl.github.io/big.js/) reference from the *doc* folder.
## Test
The *test* directory contains the test scripts for each Big number method.
The tests can be run with Node or a browser.
To test a single method, from a command-line shell at the *test* directory, use e.g.
$ node toFixed
To test all the methods
$ node every-test
For the browser, see *single-test.html* and *every-test.html* in the *test/browser* directory.
*big-vs-number.html* enables some of the methods of big.js to be compared with those of JavaScript's Number type.
## Performance
The *perf* directory contains two applications and a *lib* directory containing the BigDecimal libraries used by both.
*big-vs-bigdecimal.html* tests the performance of big.js against the JavaScript translations of two versions of BigDecimal, its use should be more or less self-explanatory.
(The GWT version doesn't work in IE 6.)
* GWT: java.math.BigDecimal
<https://github.com/iriscouch/bigdecimal.js>
* ICU4J: com.ibm.icu.math.BigDecimal
<https://github.com/dtrebbien/BigDecimal.js>
The BigDecimal in Node's npm registry is the GWT version. Despite its seeming popularity I have found it to have some serious bugs, see the Node script *perf/lib/bigdecimal_GWT/bugs.js* for examples of flaws in its *remainder*, *divide* and *compareTo* methods.
*bigtime.js* is a Node command-line application which tests the performance of big.js against the GWT version of
BigDecimal from the npm registry.
For example, to compare the time taken by the big.js `plus` method and the BigDecimal `add` method:
$ node bigtime plus 10000 40
This will time 10000 calls to each, using operands of up to 40 random digits and will check that the results match.
For help:
$ node bigtime -h
## Build
I.e. minify.
For Node, if uglify-js is installed globally ( `npm install uglify-js -g` ) then
uglifyjs -o ./big.min.js ./big.js
will create *big.min.js*.
The *big.min.js* already present was created with *Microsoft Ajax Minifier 5.11*.
## TypeScript
The [DefinitelyTyped](https://github.com/borisyankov/DefinitelyTyped) project has a TypeScript [definitions file](https://github.com/borisyankov/DefinitelyTyped/blob/master/big.js/big.js.d.ts) for big.js.
The definitions file can be added to your project via the [big.js.TypeScript.DefinitelyTyped](https://www.nuget.org/packages/big.js.TypeScript.DefinitelyTyped/0.0.1) NuGet package or via [tsd](http://definitelytyped.org/tsd/).
tsd query big.js --action install
Any questions about the TypeScript definitions file should be addressed to the DefinitelyTyped project.
## Feedback
Feedback is welcome.
Bugs/comments/questions?
Open an issue, or email
Michael
<a href="mailto:M8ch88l@gmail.com">M8ch88l@gmail.com</a>
Bitcoin donation to:
**1DppGRQSjVSMgGxuygDEHQuWEdTiVEzJYG**
Thank you
## Licence
See LICENCE.
## Change Log
####3.2.0
* 14/09/17 Aid ES6 import.
####3.1.3
* Minor documentation updates.
####3.1.2
* README typo.
####3.1.1
* API documentation update, including FAQ additions.
####3.1.0
* Renamed and exposed `TO_EXP_NEG` and `TO_EXP_POS` as `Big.E_NEG` and
`Big.E_POS`.
####3.0.2
* Remove *.npmignore*, use `files` field in *package.json* instead.
####3.0.1
* Added `sub`, `add` and `mul` aliases.
* Clean-up after lint.
####3.0.0
* 10/12/14 Added [multiple constructor functionality](http://mikemcl.github.io/big.js/#faq).
* No breaking changes or other additions, but a major code reorganisation,
so *v3* seemed appropriate.
####2.5.2
* 1/11/14 Added bower.json.
####2.5.1
* 8/06/14 Amend README requires.
####2.5.0
* 26/01/14 Added `toJSON` method so serialization uses `toString`.
####2.4.1
* 17/10/13 Conform signed zero to IEEEE 754 (2008).
####2.4.0
* 19/09/13 Throw instances of `Error`.
####2.3.0
* 16/09/13 Added `cmp` method.
####2.2.0
* 11/07/13 Added 'round up' mode.
####2.1.0
* 26/06/13 Allow e.g. `.1` and `2.`.
####2.0.0
* 12/05/13 Added `abs` method and replaced `cmp` with `eq`, `gt`, `gte`, `lt`, and `lte` methods.
####1.0.1
* Changed default value of MAX_DP to 1E6
####1.0.0
* 7/11/2012 Initial release
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,43 @@
{
"name": "big.js",
"description": "A small, fast, easy-to-use library for arbitrary-precision decimal arithmetic",
"version": "3.2.0",
"keywords": [
"arbitrary",
"precision",
"arithmetic",
"big",
"number",
"decimal",
"float",
"biginteger",
"bigdecimal",
"bignumber",
"bigint",
"bignum"
],
"repository" : {
"type": "git",
"url": "https://github.com/MikeMcl/big.js.git"
},
"main": "big.js",
"author": {
"name": "Michael Mclaughlin",
"email": "M8ch88l@gmail.com"
},
"bugs": {
"url": "https://github.com/MikeMcl/big.js/issues"
},
"engines": {
"node": "*"
},
"license": "MIT",
"scripts": {
"test": "node ./test/every-test.js",
"build": "uglifyjs big.js --source-map doc/big.js.map -c -m -o big.min.js --preamble \"/* big.js v3.2.0 https://github.com/MikeMcl/big.js/LICENCE */\""
},
"files": [
"big.js",
"big.min.js"
]
}
@@ -0,0 +1,70 @@
<a name="2.1.0"></a>
# 2.1.0 (2016-10-03)
* Fetch new emoji ([7dbe84d](https://github.com/kikobeats/emojis-list/commit/7dbe84d))
<a name="2.0.1"></a>
## 2.0.1 (2016-05-12)
* Fix typo ([3808909](https://github.com/kikobeats/emojis-list/commit/3808909))
<a name="2.0.0"></a>
# 2.0.0 (2016-05-12)
* Add update script ([f846dd6](https://github.com/kikobeats/emojis-list/commit/f846dd6))
* Block dependencies in last version ([1d9e0a5](https://github.com/kikobeats/emojis-list/commit/1d9e0a5))
* Extract main file name ([9ffe7bb](https://github.com/kikobeats/emojis-list/commit/9ffe7bb))
* Remove unnecessary files ([4c34729](https://github.com/kikobeats/emojis-list/commit/4c34729))
* Update docs, special webpack setup is not necessary ([c4aefe9](https://github.com/kikobeats/emojis-list/commit/c4aefe9))
* Update example ([1e2ae03](https://github.com/kikobeats/emojis-list/commit/1e2ae03))
* Update how to generate emojis array ([b56bad9](https://github.com/kikobeats/emojis-list/commit/b56bad9))
* Update main file based in the new interface ([996fccb](https://github.com/kikobeats/emojis-list/commit/996fccb))
<a name="1.0.3"></a>
## 1.0.3 (2016-05-12)
* Add standard as linter ([5e939d6](https://github.com/kikobeats/emojis-list/commit/5e939d6))
* Change interface ([16bc0c0](https://github.com/kikobeats/emojis-list/commit/16bc0c0))
* Generate emoji file ([fbcf8e9](https://github.com/kikobeats/emojis-list/commit/fbcf8e9))
* Remove unnecessary special doc ([2b12bec](https://github.com/kikobeats/emojis-list/commit/2b12bec))
* chore(package): update browserify to version 13.0.1 ([e2c98bf](https://github.com/kikobeats/emojis-list/commit/e2c98bf))
* chore(package): update gulp-header to version 1.8.1 ([28de793](https://github.com/kikobeats/emojis-list/commit/28de793))
<a name="1.0.2"></a>
## 1.0.2 (2016-05-05)
* fixed #2 ([9a6abe7](https://github.com/kikobeats/emojis-list/commit/9a6abe7)), closes [#2](https://github.com/kikobeats/emojis-list/issues/2)
* Fomar using standard ([5202f9f](https://github.com/kikobeats/emojis-list/commit/5202f9f))
* Update badge ([53fad9b](https://github.com/kikobeats/emojis-list/commit/53fad9b))
<a name="1.0.1"></a>
## 1.0.1 (2016-04-13)
* lock versions ([4a5d82e](https://github.com/kikobeats/emojis-list/commit/4a5d82e))
* setup devDependencies ([d1de0fc](https://github.com/kikobeats/emojis-list/commit/d1de0fc))
* update bumped ([9941038](https://github.com/kikobeats/emojis-list/commit/9941038))
* Update package.json ([6c14b74](https://github.com/kikobeats/emojis-list/commit/6c14b74))
* Update README.md ([1d9beeb](https://github.com/kikobeats/emojis-list/commit/1d9beeb))
* Update README.md ([73f215e](https://github.com/kikobeats/emojis-list/commit/73f215e))
* Update tests ([a94f7dc](https://github.com/kikobeats/emojis-list/commit/a94f7dc))
<a name="1.0.0"></a>
# 1.0.0 (2015-05-12)
* first commit ([a65b79d](https://github.com/kikobeats/emojis-list/commit/a65b79d))
* updated ([9f0564c](https://github.com/kikobeats/emojis-list/commit/9f0564c))
@@ -0,0 +1,9 @@
The MIT License (MIT)
Copyright © 2015 Kiko Beats
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,48 @@
# emojis-list
[![Dependency status](http://img.shields.io/david/Kikobeats/emojis-list.svg?style=flat-square)](https://david-dm.org/Kikobeats/emojis-list)
[![Dev Dependencies Status](http://img.shields.io/david/dev/Kikobeats/emojis-list.svg?style=flat-square)](https://david-dm.org/Kikobeats/emojis-list#info=devDependencies)
[![NPM Status](http://img.shields.io/npm/dm/emojis-list.svg?style=flat-square)](https://www.npmjs.org/package/emojis-list)
[![Donate](https://img.shields.io/badge/donate-paypal-blue.svg?style=flat-square)](https://paypal.me/kikobeats)
> Complete list of standard Unicode Hex Character Code that represent emojis.
**NOTE**: The lists is related with the Unicode Hex Character Code. The representation of the emoji depend of the system. Will be possible that the system don't have all the representations.
## Install
```bash
npm install emojis-list --save
```
If you want to use in the browser (powered by [Browserify](http://browserify.org/)):
```bash
bower install emojis-list --save
```
and later link in your HTML:
```html
<script src="bower_components/emojis-list/dist/emojis-list.js"></script>
```
## Usage
```
var emojis = require('emojis-list');
console.log(emojis[0]);
// => 🀄
```
## Related
* [emojis-unicode](https://github.com/Kikobeats/emojis-unicode) – Complete list of standard Unicode codes that represent emojis.
* [emojis-keywords](https://github.com/Kikobeats/emojis-keywords) – Complete list of am emoji shortcuts.
* [is-emoji-keyword](https://github.com/Kikobeats/is-emoji-keyword) – Check if a word is a emoji shortcut.
* [is-standard-emoji](https://github.com/kikobeats/is-standard-emoji) – Simply way to check if a emoji is a standard emoji.
* [trim-emoji](https://github.com/Kikobeats/trim-emoji) – Deletes ':' from the begin and the end of an emoji shortcut.
## License
MIT © [Kiko Beats](http://www.kikobeats.com)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,51 @@
{
"name": "emojis-list",
"description": "Complete list of standard emojis.",
"homepage": "https://github.com/Kikobeats/emojis-list",
"version": "2.1.0",
"main": "./index.js",
"author": {
"email": "josefrancisco.verdu@gmail.com",
"name": "Kiko Beats",
"url": "https://github.com/Kikobeats"
},
"repository": {
"type": "git",
"url": "git+https://github.com/kikobeats/emojis-list.git"
},
"bugs": {
"url": "https://github.com/Kikobeats/emojis-list/issues"
},
"keywords": [
"archive",
"complete",
"emoji",
"list",
"standard"
],
"devDependencies": {
"acho": "latest",
"browserify": "latest",
"cheerio": "latest",
"got": ">=5 <6",
"gulp": "latest",
"gulp-header": "latest",
"gulp-uglify": "latest",
"gulp-util": "latest",
"standard": "latest",
"vinyl-buffer": "latest",
"vinyl-source-stream": "latest"
},
"engines": {
"node": ">= 0.10"
},
"files": [
"index.js"
],
"scripts": {
"pretest": "standard update.js",
"test": "echo 'YOLO'",
"update": "node update"
},
"license": "MIT"
}
@@ -0,0 +1,452 @@
# Change Log
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
<a name="3.2.0"></a>
# [3.2.0](https://github.com/jantimon/html-webpack-plugin/compare/v3.1.0...v3.2.0) (2018-04-03)
### Bug Fixes
* **loader:** Allow to add new template parameters ([f7eac19](https://github.com/jantimon/html-webpack-plugin/commit/f7eac19)), closes [#915](https://github.com/jantimon/html-webpack-plugin/issues/915)
* **loader:** Use lodash inside the loader directly ([7b4eb7f](https://github.com/jantimon/html-webpack-plugin/commit/7b4eb7f)), closes [#786](https://github.com/jantimon/html-webpack-plugin/issues/786)
### Features
* Add meta tag option ([a7d37ca](https://github.com/jantimon/html-webpack-plugin/commit/a7d37ca))
* Support node 6.9 ([74a22c4](https://github.com/jantimon/html-webpack-plugin/commit/74a22c4)), closes [#918](https://github.com/jantimon/html-webpack-plugin/issues/918)
<a name="3.1.0"></a>
# [3.1.0](https://github.com/jantimon/html-webpack-plugin/compare/v3.0.8...v3.1.0) (2018-03-22)
### Features
* Allow to overwrite the templateParameter [#830](https://github.com/jantimon/html-webpack-plugin/issues/830) ([c5e32d3](https://github.com/jantimon/html-webpack-plugin/commit/c5e32d3))
<a name="3.0.8"></a>
## [3.0.8](https://github.com/jantimon/html-webpack-plugin/compare/v3.0.7...v3.0.8) (2018-03-22)
### Bug Fixes
* **compiler:** Fallback to 3.0.7 because of [#900](https://github.com/jantimon/html-webpack-plugin/issues/900) ([05ee29b](https://github.com/jantimon/html-webpack-plugin/commit/05ee29b))
<a name="3.0.7"></a>
## [3.0.7](https://github.com/jantimon/html-webpack-plugin/compare/v3.0.6...v3.0.7) (2018-03-19)
### Bug Fixes
* **compiler:** Set single entry name [#895](https://github.com/jantimon/html-webpack-plugin/issues/895) ([26dcb98](https://github.com/jantimon/html-webpack-plugin/commit/26dcb98))
<a name="3.0.6"></a>
## [3.0.6](https://github.com/jantimon/html-webpack-plugin/compare/v3.0.5...v3.0.6) (2018-03-06)
### Bug Fixes
* **hooks:** Call tapable.apply directly [#879](https://github.com/jantimon/html-webpack-plugin/issues/879) ([bcbb036](https://github.com/jantimon/html-webpack-plugin/commit/bcbb036))
<a name="3.0.5"></a>
## [3.0.5](https://github.com/jantimon/html-webpack-plugin/compare/v3.0.2...v3.0.5) (2018-03-06)
### Bug Fixes
* **entries:** do not ignore JS if there is also CSS ([020b714](https://github.com/jantimon/html-webpack-plugin/commit/020b714))
* **entries:** Don't add css entries twice ([0348d6b](https://github.com/jantimon/html-webpack-plugin/commit/0348d6b))
* **hooks:** Remove deprecated tapable calls [#879](https://github.com/jantimon/html-webpack-plugin/issues/879) ([2288f20](https://github.com/jantimon/html-webpack-plugin/commit/2288f20))
<a name="3.0.4"></a>
## [3.0.4](https://github.com/jantimon/html-webpack-plugin/compare/v3.0.2...v3.0.4) (2018-03-01)
### Bug Fixes
* **entries:** Don't add css entries twice ([e890f23](https://github.com/jantimon/html-webpack-plugin/commit/e890f23))
<a name="3.0.3"></a>
## [3.0.3](https://github.com/jantimon/html-webpack-plugin/compare/v3.0.2...v3.0.3) (2018-03-01)
### Refactor
* **performance:** Reduce the amount of chunk information gathered based on #825 ([06c59a7](https://github.com/jantimon/html-webpack-plugin/commit/06c59a7))
<a name="3.0.2"></a>
## [3.0.2](https://github.com/jantimon/html-webpack-plugin/compare/v3.0.1...v3.0.2) (2018-03-01)
### Bug Fixes
* **query-loader:** In case no query is provided, return an empty object. This fixes #727 ([7587754](https://github.com/jantimon/html-webpack-plugin/commit/7587754))
<a name="3.0.1"></a>
## [3.0.1](https://github.com/jantimon/html-webpack-plugin/compare/v3.0.0...v3.0.1) (2018-03-01)
### Bug Fixes
* **package:** Remove the extract-text-webpack-plugin peer dependency ([57411a9](https://github.com/jantimon/html-webpack-plugin/commit/57411a9))
<a name="3.0.0"></a>
## [3.0.0](https://github.com/jantimon/html-webpack-plugin/compare/v2.30.1...v3.0.0) (2018-28-02)
### Features
* Add support for the new [webpack tapable](https://github.com/webpack/tapable) to be compatible with webpack 4.x
* Remove bluebird dependency
### BREAKING CHANGES
* Similar to webpack 4.x the support for node versions older than 6 are no longer supported
<a name="2.30.1"></a>
## 2.30.1
* Revert part the performance optimization ([#723](https://github.com/jantimon/html-webpack-plugin/pull/723)) because of [#753](https://github.com/jantimon/html-webpack-plugin/issues/753).
<a name="2.30.0"></a>
## 2.30.0
* Add manual sort
* Performance improvements ([#723](https://github.com/jantimon/html-webpack-plugin/pull/723))
<a name="2.29.0"></a>
## 2.29.0
* Add support for Webpack 3
<a name="2.28.0"></a>
## 2.28.0
* Backport 3.x void tag for plugin authors
<a name="2.27.1"></a>
## 2.27.1
* Revert 2.25.0 loader resolving
<a name="2.27.0"></a>
## 2.27.0
* Fix a chunksorter webpack 2 issue ([#569](https://github.com/jantimon/html-webpack-plugin/pull/569))
* Fix template path resolving ([#542](https://github.com/jantimon/html-webpack-plugin/pull/542))
<a name="2.26.0"></a>
## 2.26.0
* Allow plugins to add attributes without values to the `<script>` and `<link>` tags
<a name="2.25.0"></a>
## 2.25.0
* Clearer loader output
* Add basic support for webpack 2
<a name="2.24.1"></a>
## 2.24.1
* Hide event deprecated warning of 'applyPluginsAsyncWaterfall' for html-webpack-plugin-after-emit and improve the warning message.
<a name="2.24.0"></a>
## 2.24.0
* Update dependencies
* Add deprecate warning for plugins not returning a result
* Add [path] for favicons
<a name="2.23.0"></a>
## 2.23.0
* Update dependencies
* Stop automated tests for webpack 2 beta because of [#401](https://github.com/jantimon/html-webpack-plugin/issues/401)
<a name="2.22.0"></a>
## 2.22.0
* Update dependencies
<a name="2.21.1"></a>
## 2.21.1
* Better error handling ([#354](https://github.com/jantimon/html-webpack-plugin/pull/354))
<a name="2.21.0"></a>
## 2.21.0
* Add `html-webpack-plugin-alter-asset-tags` event to allow plugins to adjust the script/link tags
<a name="2.20.0"></a>
## 2.20.0
* Exclude chunks works now even if combined with dependency sort
<a name="2.19.0"></a>
## 2.19.0
* Add `html-webpack-plugin-alter-chunks` event for custom chunk sorting and interpolation
<a name="2.18.0"></a>
## 2.18.0
* Updated all dependencies
<a name="2.17.0"></a>
## 2.17.0
* Add `type` attribute to `script` element to prevent issues in Safari 9.1.1
<a name="2.16.2"></a>
## 2.16.2
* Fix bug introduced by 2.16.2. Fixes [#315](https://github.com/jantimon/html-webpack-plugin/issues/315)
<a name="2.16.1"></a>
## 2.16.1
* Fix hot module replacement for webpack 2.x
<a name="2.16.0"></a>
## 2.16.0
* Add support for dynamic filenames like index[hash].html
<a name="2.15.0"></a>
## 2.15.0
* Add full unit test coverage for the webpack 2 beta version
* For webpack 2 the default sort will be 'dependency' instead of 'id'
* Upgrade dependencies
<a name="2.14.0"></a>
## 2.14.0
* Export publicPath to the template
* Add example for inlining css and js
<a name="2.13.0"></a>
## 2.13.0
* Add support for absolute output file names
* Add support for relative file names outside the output path
<a name="2.12.0"></a>
## 2.12.0
* Basic Webpack 2.x support #225
<a name="2.11.0"></a>
## 2.11.0
* Add `xhtml` option which is turned of by default. When activated it will inject self closed `<link href=".." />` tags instead of unclosed `<link href="..">` tags. ([#255](https://github.com/ampedandwired/html-webpack-plugin/pull/255))
* Add support for webpack placeholders inside the public path e.g. `'/dist/[hash]/'`. ([#249](https://github.com/ampedandwired/html-webpack-plugin/pull/249))
<a name="2.10.0"></a>
## 2.10.0
* Add `hash` field to the chunk object
* Add `compilation` field to the templateParam object ([#237](https://github.com/ampedandwired/html-webpack-plugin/issues/237))
* Add `html-webpack-plugin-before-html-generation` event
* Improve error messages
<a name="2.9.0"></a>
## 2.9.0
* Fix favicon path ([#185](https://github.com/ampedandwired/html-webpack-plugin/issues/185), [#208](https://github.com/ampedandwired/html-webpack-plugin/issues/208), [#215](https://github.com/ampedandwired/html-webpack-plugin/pull/215))
<a name="2.8.2"></a>
## 2.8.2
* Support relative URLs on Windows ([#205](https://github.com/ampedandwired/html-webpack-plugin/issues/205))
<a name="2.8.1"></a>
## 2.8.1
* Caching improvements ([#204](https://github.com/ampedandwired/html-webpack-plugin/issues/204))
<a name="2.8.0"></a>
## 2.8.0
* Add `dependency` mode for `chunksSortMode` to sort chunks based on their dependencies with each other
<a name="2.7.2"></a>
## 2.7.2
* Add support for require in js templates
<a name="2.7.1"></a>
## 2.7.1
* Refactoring
* Fix relative windows path
<a name="2.6.5"></a>
## 2.6.5
* Minor refactoring
<a name="2.6.4"></a>
## 2.6.4
* Fix for `"Uncaught TypeError: __webpack_require__(...) is not a function"`
* Fix incomplete cache modules causing "HtmlWebpackPlugin Error: No source available"
* Fix some issues on Windows
<a name="2.6.3"></a>
## 2.6.3
* Prevent parsing the base template with the html-loader
<a name="2.6.2"></a>
## 2.6.2
* Fix `lodash` resolve error ([#172](https://github.com/ampedandwired/html-webpack-plugin/issues/172))
<a name="2.6.1"></a>
## 2.6.1
* Fix missing module ([#164](https://github.com/ampedandwired/html-webpack-plugin/issues/164))
<a name="2.6.0"></a>
## 2.6.0
* Move compiler to its own file
* Improve error messages
* Fix global HTML_WEBPACK_PLUGIN variable
<a name="2.5.0"></a>
## 2.5.0
* Support `lodash` template's HTML _"escape"_ delimiter (`<%- %>`)
* Fix bluebird warning ([#130](https://github.com/ampedandwired/html-webpack-plugin/issues/130))
* Fix an issue where incomplete cache modules were used
<a name="2.4.0"></a>
## 2.4.0
* Don't recompile if the assets didn't change
<a name="2.3.0"></a>
## 2.3.0
* Add events `html-webpack-plugin-before-html-processing`, `html-webpack-plugin-after-html-processing`, `html-webpack-plugin-after-emit` to allow other plugins to alter the html this plugin executes
<a name="2.2.0"></a>
## 2.2.0
* Inject css and js even if the html file is incomplete ([#135](https://github.com/ampedandwired/html-webpack-plugin/issues/135))
* Update dependencies
<a name="2.1.0"></a>
## 2.1.0
* Synchronize with the stable `@1` version
<a name="2.0.4"></a>
## 2.0.4
* Fix `minify` option
* Fix missing hash interpolation in publicPath
<a name="2.0.3"></a>
## 2.0.3
* Add support for webpack.BannerPlugin
<a name="2.0.2"></a>
## 2.0.2
* Add support for loaders in templates ([#41](https://github.com/ampedandwired/html-webpack-plugin/pull/41))
* Remove `templateContent` option from configuration
* Better error messages
* Update dependencies
<a name="1.7.0"></a>
## 1.7.0
* Add `chunksSortMode` option to configuration to control how chunks should be sorted before they are included to the html
* Don't insert async chunks into html ([#95](https://github.com/ampedandwired/html-webpack-plugin/issues/95))
* Update dependencies
<a name="1.6.2"></a>
## 1.6.2
* Fix paths on Windows
* Fix missing hash interpolation in publicPath
* Allow only `false` or `object` in `minify` configuration option
<a name="1.6.1"></a>
## 1.6.1
* Add `size` field to the chunk object
* Fix stylesheet `<link>`s being discarded when used with `"inject: 'head'"`
* Update dependencies
<a name="1.6.0"></a>
## 1.6.0
* Support placing templates in subfolders
* Don't include chunks with undefined name ([#60](https://github.com/ampedandwired/html-webpack-plugin/pull/60))
* Don't include async chunks
<a name="1.5.2"></a>
## 1.5.2
* Update dependencies (lodash)
<a name="1.5.1"></a>
## 1.5.1
* Fix error when manifest is specified ([#56](https://github.com/ampedandwired/html-webpack-plugin/issues/56))
<a name="1.5.0"></a>
## 1.5.0
* Allow to inject javascript files into the head of the html page
* Fix error reporting
<a name="1.4.0"></a>
## 1.4.0
* Add `favicon.ico` option
* Add html minifcation
<a name="1.2.0"></a>
## 1.2.0
* Set charset using HTML5 meta attribute
* Reload upon change when using webpack watch mode
* Generate manifest attribute when using
[appcache-webpack-plugin](https://github.com/lettertwo/appcache-webpack-plugin)
* Optionally add webpack hash as a query string to resources included in the HTML
(`hash: true`) for cache busting
* CSS files generated using webpack (for example, by using the
[extract-text-webpack-plugin](https://github.com/webpack/extract-text-webpack-plugin))
are now automatically included into the generated HTML
* More detailed information about the files generated by webpack is now available
to templates in the `o.htmlWebpackPlugin.files` attribute. See readme for more
details. This new attribute deprecates the old `o.htmlWebpackPlugin.assets` attribute.
* The `templateContent` option can now be a function that returns the template string to use
* Expose webpack configuration to templates (`o.webpackConfig`)
* Sort chunks to honour dependencies between them (useful for use with CommonsChunkPlugin).
@@ -0,0 +1,20 @@
Copyright JS Foundation and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,415 @@
[![npm][npm]][npm-url]
[![node][node]][node-url]
![npm](https://img.shields.io/npm/dw/html-webpack-plugin.svg)
[![deps][deps]][deps-url]
[![tests][tests]][tests-url]
[![Backers on Open Collective](https://opencollective.com/html-webpack-plugin/backers/badge.svg)](#backers)
[![Sponsors on Open Collective](https://opencollective.com/html-webpack-plugin/sponsors/badge.svg)](#sponsors)
<div align="center">
<img width="200" height="200" src="https://worldvectorlogo.com/logos/html5.svg">
<a href="https://github.com/webpack/webpack">
<img width="200" height="200"
src="https://webpack.js.org/assets/icon-square-big.svg">
</a>
<div>
<img width="100" height="100" title="Webpack Plugin" src="http://michael-ciniawsky.github.io/postcss-load-plugins/logo.svg">
</div>
<h1>HTML Webpack Plugin</h1>
<p>Plugin that simplifies creation of HTML files to serve your bundles</p>
</div>
<h2 align="center">Install</h2>
```bash
npm i --save-dev html-webpack-plugin
```
```bash
yarn add --dev html-webpack-plugin
```
This is a [webpack](http://webpack.js.org/) plugin that simplifies creation of HTML files to serve your `webpack` bundles. This is especially useful for `webpack` bundles that include a hash in the filename which changes every compilation. You can either let the plugin generate an HTML file for you, supply
your own template using `lodash` templates or use your own loader.
### `Plugins`
The `html-webpack-plugin` provides [hooks](https://github.com/jantimon/html-webpack-plugin#events) to extend it to your needs. There are already some really powerful plugins which can be integrated with zero configuration
* [webpack-subresource-integrity](https://www.npmjs.com/package/webpack-subresource-integrity) for enhanced asset security
* [appcache-webpack-plugin](https://github.com/lettertwo/appcache-webpack-plugin) for iOS and Android offline usage
* [favicons-webpack-plugin](https://github.com/jantimon/favicons-webpack-plugin) which generates favicons and icons for iOS, Android and desktop browsers
* [html-webpack-harddisk-plugin](https://github.com/jantimon/html-webpack-harddisk-plugin) can be used to always write to disk the html file, useful when webpack-dev-server / HMR are being used
* [html-webpack-inline-source-plugin](https://github.com/DustinJackson/html-webpack-inline-source-plugin) to inline your assets in the resulting HTML file
* [html-webpack-inline-svg-plugin](https://github.com/thegc/html-webpack-inline-svg-plugin) to inline SVGs in the resulting HTML file.
* [html-webpack-exclude-assets-plugin](https://github.com/jamesjieye/html-webpack-exclude-assets-plugin) for excluding assets using regular expressions
* [html-webpack-include-assets-plugin](https://github.com/jharris4/html-webpack-include-assets-plugin) for including lists of js or css file paths (such as those copied by the copy-webpack-plugin).
* [script-ext-html-webpack-plugin](https://github.com/numical/script-ext-html-webpack-plugin) to add `async`, `defer` or `module` attributes to your `<script>` elements, or even inline them
* [style-ext-html-webpack-plugin](https://github.com/numical/style-ext-html-webpack-plugin) to convert your `<link>`s to external stylesheets into `<style>` elements containing internal CSS
* [resource-hints-webpack-plugin](https://github.com/jantimon/resource-hints-webpack-plugin) to add resource hints for faster initial page loads using `<link rel='preload'>` and `<link rel='prefetch'>`
* [preload-webpack-plugin](https://github.com/GoogleChrome/preload-webpack-plugin) for automatically wiring up asynchronous (and other types) of JavaScript chunks using `<link rel='preload'>` helping with lazy-loading
* [link-media-html-webpack-plugin](https://github.com/yaycmyk/link-media-html-webpack-plugin) allows for injected stylesheet `<link />` tags to have their media attribute set automatically; useful for providing specific desktop/mobile/print etc. stylesheets that the browser will conditionally download
* [inline-chunk-manifest-html-webpack-plugin](https://github.com/jouni-kantola/inline-chunk-manifest-html-webpack-plugin) for inlining webpack's chunk manifest. Default extracts manifest and inlines in `<head>`
* [html-webpack-inline-style-plugin](https://github.com/djaax/html-webpack-inline-style-plugin) for inlining styles to HTML elements using [juice](https://github.com/Automattic/juice). Useful for email generation automatisation.
* [html-webpack-exclude-empty-assets-plugin](https://github.com/KnisterPeter/html-webpack-exclude-empty-assets-plugin) removes empty assets from being added to the html. This fixes some problems with extract-text-plugin with webpack 4.
* [webpack-concat-plugin](https://github.com/hxlniada/webpack-concat-plugin) for concat and uglify files that needn't to be webpack bundles(for legacy files) and inject to html-webpack-plugin.
<h2 align="center">Usage</h2>
The plugin will generate an HTML5 file for you that includes all your `webpack`
bundles in the body using `script` tags. Just add the plugin to your `webpack`
config as follows:
**webpack.config.js**
```js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: 'index.js',
output: {
path: __dirname + '/dist',
filename: 'index_bundle.js'
},
plugins: [
new HtmlWebpackPlugin()
]
}
```
This will generate a file `dist/index.html` containing the following
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Webpack App</title>
</head>
<body>
<script src="index_bundle.js"></script>
</body>
</html>
```
If you have multiple `webpack` entry points, they will all be included with `script` tags in the generated HTML.
If you have any CSS assets in webpack's output (for example, CSS extracted with the [ExtractTextPlugin](https://github.com/webpack/extract-text-webpack-plugin))
then these will be included with `<link>` tags in the HTML head.
If you have plugins that make use of it, `html-webpack-plugin` should be ordered first before any of the integrated plugins.
<h2 align="center">Options</h2>
You can pass a hash of configuration options to `html-webpack-plugin`.
Allowed values are as follows
|Name|Type|Default|Description|
|:--:|:--:|:-----:|:----------|
|**[`title`](#)**|`{String}`|``|The title to use for the generated HTML document|
|**[`filename`](#)**|`{String}`|`'index.html'`|The file to write the HTML to. Defaults to `index.html`. You can specify a subdirectory here too (eg: `assets/admin.html`)|
|**[`template`](#)**|`{String}`|``|`webpack` require path to the template. Please see the [docs](https://github.com/jantimon/html-webpack-plugin/blob/master/docs/template-option.md) for details|
|**[`templateParameters`](#)**|`{Boolean\|Object\|Function}`|``| Allows to overwrite the parameters used in the template |
|**[`inject`](#)**|`{Boolean\|String}`|`true`|`true \|\| 'head' \|\| 'body' \|\| false` Inject all assets into the given `template` or `templateContent`. When passing `true` or `'body'` all javascript resources will be placed at the bottom of the body element. `'head'` will place the scripts in the head element|
|**[`favicon`](#)**|`{String}`|``|Adds the given favicon path to the output HTML|
|**[`meta`](#)**|`{Object}`|`{}`|Allows to inject `meta`-tags. E.g. `meta: {viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no'}`|
|**[`minify`](#)**|`{Boolean\|Object}`|`true`|Pass [html-minifier](https://github.com/kangax/html-minifier#options-quick-reference)'s options as object to minify the output|
|**[`hash`](#)**|`{Boolean}`|`false`|If `true` then append a unique `webpack` compilation hash to all included scripts and CSS files. This is useful for cache busting|
|**[`cache`](#)**|`{Boolean}`|`true`|Emit the file only if it was changed|
|**[`showErrors`](#)**|`{Boolean}`|`true`|Errors details will be written into the HTML page|
|**[`chunks`](#)**|`{?}`|`?`|Allows you to add only some chunks (e.g only the unit-test chunk)|
|**[`chunksSortMode`](#plugins)**|`{String\|Function}`|`auto`|Allows to control how chunks should be sorted before they are included to the HTML. Allowed values are `'none' \| 'auto' \| 'dependency' \| 'manual' \| {Function}`|
|**[`excludeChunks`](#)**|`{Array.<string>}`|``|Allows you to skip some chunks (e.g don't add the unit-test chunk)|
|**[`xhtml`](#)**|`{Boolean}`|`false`|If `true` render the `link` tags as self-closing (XHTML compliant)|
Here's an example webpack config illustrating how to use these options
**webpack.config.js**
```js
{
entry: 'index.js',
output: {
path: __dirname + '/dist',
filename: 'index_bundle.js'
},
plugins: [
new HtmlWebpackPlugin({
title: 'My App',
filename: 'assets/admin.html'
})
]
}
```
### `Generating Multiple HTML Files`
To generate more than one HTML file, declare the plugin more than
once in your plugins array
**webpack.config.js**
```js
{
entry: 'index.js',
output: {
path: __dirname + '/dist',
filename: 'index_bundle.js'
},
plugins: [
new HtmlWebpackPlugin(), // Generates default index.html
new HtmlWebpackPlugin({ // Also generate a test.html
filename: 'test.html',
template: 'src/assets/test.html'
})
]
}
```
### `Writing Your Own Templates`
If the default generated HTML doesn't meet your needs you can supply
your own template. The easiest way is to use the `template` option and pass a custom HTML file.
The html-webpack-plugin will automatically inject all necessary CSS, JS, manifest
and favicon files into the markup.
```js
plugins: [
new HtmlWebpackPlugin({
title: 'Custom template',
// Load a custom template (lodash by default see the FAQ for details)
template: 'index.html'
})
]
```
**index.html**
```html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
</body>
</html>
```
If you already have a template loader, you can use it to parse the template.
Please note that this will also happen if you specifiy the html-loader and use `.html` file as template.
**webpack.config.js**
```js
module: {
loaders: [
{ test: /\.hbs$/, loader: "handlebars" }
]
},
plugins: [
new HtmlWebpackPlugin({
title: 'Custom template using Handlebars',
template: 'index.hbs'
})
]
```
You can use the `lodash` syntax out of the box. If the `inject` feature doesn't fit your needs and you want full control over the asset placement use the [default template](https://github.com/jaketrent/html-webpack-template/blob/86f285d5c790a6c15263f5cc50fd666d51f974fd/index.html) of the [html-webpack-template project](https://github.com/jaketrent/html-webpack-template) as a starting point for writing your own.
The following variables are available in the template:
- `htmlWebpackPlugin`: data specific to this plugin
- `htmlWebpackPlugin.files`: a massaged representation of the
`assetsByChunkName` attribute of webpack's [stats](https://github.com/webpack/docs/wiki/node.js-api#stats)
object. It contains a mapping from entry point name to the bundle filename, eg:
```json
"htmlWebpackPlugin": {
"files": {
"css": [ "main.css" ],
"js": [ "assets/head_bundle.js", "assets/main_bundle.js"],
"chunks": {
"head": {
"entry": "assets/head_bundle.js",
"css": [ "main.css" ]
},
"main": {
"entry": "assets/main_bundle.js",
"css": []
},
}
}
}
```
If you've set a publicPath in your webpack config this will be reflected
correctly in this assets hash.
- `htmlWebpackPlugin.options`: the options hash that was passed to
the plugin. In addition to the options actually used by this plugin,
you can use this hash to pass arbitrary data through to your template.
- `webpack`: the webpack [stats](https://github.com/webpack/docs/wiki/node.js-api#stats)
object. Note that this is the stats object as it was at the time the HTML template
was emitted and as such may not have the full set of stats that are available
after the webpack run is complete.
- `webpackConfig`: the webpack configuration that was used for this compilation. This
can be used, for example, to get the `publicPath` (`webpackConfig.output.publicPath`).
- `compilation`: the webpack [compilation](https://webpack.js.org/api/compilation/) object.
This can be used, for example, to get the contents of processed assets and inline them
directly in the page, through `compilation.assets[...].source()`
(see [the inline template example](examples/inline/template.jade)).
### `Filtering Chunks`
To include only certain chunks you can limit the chunks being used
**webpack.config.js**
```js
plugins: [
new HtmlWebpackPlugin({
chunks: ['app']
})
]
```
It is also possible to exclude certain chunks by setting the `excludeChunks` option
**webpack.config.js**
```js
plugins: [
new HtmlWebpackPlugin({
excludeChunks: [ 'dev-helper' ]
})
]
```
### `Events`
To allow other [plugins](https://github.com/webpack/docs/wiki/plugins) to alter the HTML this plugin executes the following events:
#### `Sync`
* `html-webpack-plugin-alter-chunks`
#### `Async`
* `html-webpack-plugin-before-html-generation`
* `html-webpack-plugin-before-html-processing`
* `html-webpack-plugin-alter-asset-tags`
* `html-webpack-plugin-after-html-processing`
* `html-webpack-plugin-after-emit`
Example implementation: [html-webpack-harddisk-plugin](https://github.com/jantimon/html-webpack-harddisk-plugin)
**plugin.js**
```js
function MyPlugin(options) {
// Configure your plugin with options...
}
MyPlugin.prototype.apply = function (compiler) {
compiler.plugin('compilation', (compilation) => {
console.log('The compiler is starting a new compilation...');
compilation.plugin(
'html-webpack-plugin-before-html-processing',
(data, cb) => {
data.html += 'The Magic Footer'
cb(null, data)
}
)
})
}
module.exports = MyPlugin
```
**webpack.config.js**
```js
plugins: [
new MyPlugin({ options: '' })
]
```
Note that the callback must be passed the HtmlWebpackPluginData in order to pass this onto any other plugins listening on the same `html-webpack-plugin-before-html-processing` event
<h2 align="center">Maintainers</h2>
<table>
<tbody>
<tr>
<td align="center">
<img width="150" height="150"
src="https://avatars3.githubusercontent.com/u/4113649?v=3&s=150">
</br>
<a href="https://github.com/jantimon">Jan Nicklas</a>
</td>
<td align="center">
<img width="150" height="150"
src="https://avatars2.githubusercontent.com/u/4112409?v=3&s=150">
</br>
<a href="https://github.com/mastilver">Thomas Sileghem</a>
</td>
</tr>
<tbody>
</table>
[npm]: https://img.shields.io/npm/v/html-webpack-plugin.svg
[npm-url]: https://npmjs.com/package/html-webpack-plugin
[node]: https://img.shields.io/node/v/html-webpack-plugin.svg
[node-url]: https://nodejs.org
[deps]: https://david-dm.org/jantimon/html-webpack-plugin.svg
[deps-url]: https://david-dm.org/jantimon/html-webpack-plugin
[tests]: http://img.shields.io/travis/jantimon/html-webpack-plugin.svg
[tests-url]: https://travis-ci.org/jantimon/html-webpack-plugin
## Contributors
This project exists thanks to all the people who contribute.
You're free to contribute to this project by submitting [issues](https://github.com/jantimon/html-webpack-plugin/issues) and/or [pull requests](https://github.com/jantimon/html-webpack-plugin/pulls). This project is test-driven, so keep in mind that every change and new feature should be covered by tests.
This project uses the [semistandard code style](https://github.com/Flet/semistandard).
<a href="https://github.com/jantimon/html-webpack-plugin/graphs/contributors"><img src="https://opencollective.com/html-webpack-plugin/contributors.svg?width=890&button=false" /></a>
## Backers
Thank you to all our backers! 🙏 [Become a backer](https://opencollective.com/html-webpack-plugin#backer)
<a href="https://opencollective.com/html-webpack-plugin/backer/0/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/0/avatar.svg?requireActive=false"></a>
<a href="https://opencollective.com/html-webpack-plugin/backer/1/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/1/avatar.svg?requireActive=false"></a>
<a href="https://opencollective.com/html-webpack-plugin/backer/2/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/2/avatar.svg?requireActive=false"></a>
<a href="https://opencollective.com/html-webpack-plugin/backer/3/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/3/avatar.svg?requireActive=false"></a>
<a href="https://opencollective.com/html-webpack-plugin/backer/4/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/4/avatar.svg?requireActive=false"></a>
<a href="https://opencollective.com/html-webpack-plugin/backer/5/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/5/avatar.svg?requireActive=false"></a>
<a href="https://opencollective.com/html-webpack-plugin/backer/6/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/6/avatar.svg?requireActive=false"></a>
<a href="https://opencollective.com/html-webpack-plugin/backer/7/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/7/avatar.svg?requireActive=false"></a>
<a href="https://opencollective.com/html-webpack-plugin/backer/8/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/8/avatar.svg?requireActive=false"></a>
<a href="https://opencollective.com/html-webpack-plugin/backer/9/website?requireActive=false" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/backer/9/avatar.svg?requireActive=false"></a>
## Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor](https://opencollective.com/html-webpack-plugin#sponsor)
<a href="https://opencollective.com/html-webpack-plugin/sponsor/0/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/html-webpack-plugin/sponsor/1/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/html-webpack-plugin/sponsor/2/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/html-webpack-plugin/sponsor/3/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/html-webpack-plugin/sponsor/4/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/html-webpack-plugin/sponsor/5/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/html-webpack-plugin/sponsor/6/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/html-webpack-plugin/sponsor/7/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/html-webpack-plugin/sponsor/8/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/html-webpack-plugin/sponsor/9/website" target="_blank"><img src="https://opencollective.com/html-webpack-plugin/sponsor/9/avatar.svg"></a>
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
</body>
</html>
@@ -0,0 +1,729 @@
'use strict';
// use Polyfill for util.promisify in node versions < v8
const promisify = require('util.promisify');
const vm = require('vm');
const fs = require('fs');
const _ = require('lodash');
const path = require('path');
const childCompiler = require('./lib/compiler.js');
const prettyError = require('./lib/errors.js');
const chunkSorter = require('./lib/chunksorter.js');
const fsStatAsync = promisify(fs.stat);
const fsReadFileAsync = promisify(fs.readFile);
class HtmlWebpackPlugin {
constructor (options) {
// Default options
this.options = _.extend({
template: path.join(__dirname, 'default_index.ejs'),
templateParameters: templateParametersGenerator,
filename: 'index.html',
hash: false,
inject: true,
compile: true,
favicon: false,
minify: false,
cache: true,
showErrors: true,
chunks: 'all',
excludeChunks: [],
chunksSortMode: 'auto',
meta: {},
title: 'Webpack App',
xhtml: false
}, options);
}
apply (compiler) {
const self = this;
let isCompilationCached = false;
let compilationPromise;
this.options.template = this.getFullTemplatePath(this.options.template, compiler.context);
// convert absolute filename into relative so that webpack can
// generate it at correct location
const filename = this.options.filename;
if (path.resolve(filename) === path.normalize(filename)) {
this.options.filename = path.relative(compiler.options.output.path, filename);
}
// setup hooks for webpack 4
if (compiler.hooks) {
compiler.hooks.compilation.tap('HtmlWebpackPluginHooks', compilation => {
const SyncWaterfallHook = require('tapable').SyncWaterfallHook;
const AsyncSeriesWaterfallHook = require('tapable').AsyncSeriesWaterfallHook;
compilation.hooks.htmlWebpackPluginAlterChunks = new SyncWaterfallHook(['chunks', 'objectWithPluginRef']);
compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration = new AsyncSeriesWaterfallHook(['pluginArgs']);
compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing = new AsyncSeriesWaterfallHook(['pluginArgs']);
compilation.hooks.htmlWebpackPluginAlterAssetTags = new AsyncSeriesWaterfallHook(['pluginArgs']);
compilation.hooks.htmlWebpackPluginAfterHtmlProcessing = new AsyncSeriesWaterfallHook(['pluginArgs']);
compilation.hooks.htmlWebpackPluginAfterEmit = new AsyncSeriesWaterfallHook(['pluginArgs']);
});
}
// Backwards compatible version of: compiler.hooks.make.tapAsync()
(compiler.hooks ? compiler.hooks.make.tapAsync.bind(compiler.hooks.make, 'HtmlWebpackPlugin') : compiler.plugin.bind(compiler, 'make'))((compilation, callback) => {
// Compile the template (queued)
compilationPromise = childCompiler.compileTemplate(self.options.template, compiler.context, self.options.filename, compilation)
.catch(err => {
compilation.errors.push(prettyError(err, compiler.context).toString());
return {
content: self.options.showErrors ? prettyError(err, compiler.context).toJsonHtml() : 'ERROR',
outputName: self.options.filename
};
})
.then(compilationResult => {
// If the compilation change didnt change the cache is valid
isCompilationCached = compilationResult.hash && self.childCompilerHash === compilationResult.hash;
self.childCompilerHash = compilationResult.hash;
self.childCompilationOutputName = compilationResult.outputName;
callback();
return compilationResult.content;
});
});
// Backwards compatible version of: compiler.plugin.emit.tapAsync()
(compiler.hooks ? compiler.hooks.emit.tapAsync.bind(compiler.hooks.emit, 'HtmlWebpackPlugin') : compiler.plugin.bind(compiler, 'emit'))((compilation, callback) => {
const applyPluginsAsyncWaterfall = self.applyPluginsAsyncWaterfall(compilation);
// Get chunks info as json
// Note: we're excluding stuff that we don't need to improve toJson serialization speed.
const chunkOnlyConfig = {
assets: false,
cached: false,
children: false,
chunks: true,
chunkModules: false,
chunkOrigins: false,
errorDetails: false,
hash: false,
modules: false,
reasons: false,
source: false,
timings: false,
version: false
};
const allChunks = compilation.getStats().toJson(chunkOnlyConfig).chunks;
// Filter chunks (options.chunks and options.excludeCHunks)
let chunks = self.filterChunks(allChunks, self.options.chunks, self.options.excludeChunks);
// Sort chunks
chunks = self.sortChunks(chunks, self.options.chunksSortMode, compilation);
// Let plugins alter the chunks and the chunk sorting
if (compilation.hooks) {
chunks = compilation.hooks.htmlWebpackPluginAlterChunks.call(chunks, { plugin: self });
} else {
// Before Webpack 4
chunks = compilation.applyPluginsWaterfall('html-webpack-plugin-alter-chunks', chunks, { plugin: self });
}
// Get assets
const assets = self.htmlWebpackPluginAssets(compilation, chunks);
// If this is a hot update compilation, move on!
// This solves a problem where an `index.html` file is generated for hot-update js files
// It only happens in Webpack 2, where hot updates are emitted separately before the full bundle
if (self.isHotUpdateCompilation(assets)) {
return callback();
}
// If the template and the assets did not change we don't have to emit the html
const assetJson = JSON.stringify(self.getAssetFiles(assets));
if (isCompilationCached && self.options.cache && assetJson === self.assetJson) {
return callback();
} else {
self.assetJson = assetJson;
}
Promise.resolve()
// Favicon
.then(() => {
if (self.options.favicon) {
return self.addFileToAssets(self.options.favicon, compilation)
.then(faviconBasename => {
let publicPath = compilation.mainTemplate.getPublicPath({hash: compilation.hash}) || '';
if (publicPath && publicPath.substr(-1) !== '/') {
publicPath += '/';
}
assets.favicon = publicPath + faviconBasename;
});
}
})
// Wait for the compilation to finish
.then(() => compilationPromise)
.then(compiledTemplate => {
// Allow to use a custom function / string instead
if (self.options.templateContent !== undefined) {
return self.options.templateContent;
}
// Once everything is compiled evaluate the html factory
// and replace it with its content
return self.evaluateCompilationResult(compilation, compiledTemplate);
})
// Allow plugins to make changes to the assets before invoking the template
// This only makes sense to use if `inject` is `false`
.then(compilationResult => applyPluginsAsyncWaterfall('html-webpack-plugin-before-html-generation', false, {
assets: assets,
outputName: self.childCompilationOutputName,
plugin: self
})
.then(() => compilationResult))
// Execute the template
.then(compilationResult => typeof compilationResult !== 'function'
? compilationResult
: self.executeTemplate(compilationResult, chunks, assets, compilation))
// Allow plugins to change the html before assets are injected
.then(html => {
const pluginArgs = {html: html, assets: assets, plugin: self, outputName: self.childCompilationOutputName};
return applyPluginsAsyncWaterfall('html-webpack-plugin-before-html-processing', true, pluginArgs);
})
.then(result => {
const html = result.html;
const assets = result.assets;
// Prepare script and link tags
const assetTags = self.generateHtmlTags(assets);
const pluginArgs = {head: assetTags.head, body: assetTags.body, plugin: self, chunks: chunks, outputName: self.childCompilationOutputName};
// Allow plugins to change the assetTag definitions
return applyPluginsAsyncWaterfall('html-webpack-plugin-alter-asset-tags', true, pluginArgs)
.then(result => self.postProcessHtml(html, assets, { body: result.body, head: result.head })
.then(html => _.extend(result, {html: html, assets: assets})));
})
// Allow plugins to change the html after assets are injected
.then(result => {
const html = result.html;
const assets = result.assets;
const pluginArgs = {html: html, assets: assets, plugin: self, outputName: self.childCompilationOutputName};
return applyPluginsAsyncWaterfall('html-webpack-plugin-after-html-processing', true, pluginArgs)
.then(result => result.html);
})
.catch(err => {
// In case anything went wrong the promise is resolved
// with the error message and an error is logged
compilation.errors.push(prettyError(err, compiler.context).toString());
// Prevent caching
self.hash = null;
return self.options.showErrors ? prettyError(err, compiler.context).toHtml() : 'ERROR';
})
.then(html => {
// Replace the compilation result with the evaluated html code
compilation.assets[self.childCompilationOutputName] = {
source: () => html,
size: () => html.length
};
})
.then(() => applyPluginsAsyncWaterfall('html-webpack-plugin-after-emit', false, {
html: compilation.assets[self.childCompilationOutputName],
outputName: self.childCompilationOutputName,
plugin: self
}).catch(err => {
console.error(err);
return null;
}).then(() => null))
// Let webpack continue with it
.then(() => {
callback();
});
});
}
/**
* Evaluates the child compilation result
* Returns a promise
*/
evaluateCompilationResult (compilation, source) {
if (!source) {
return Promise.reject('The child compilation didn\'t provide a result');
}
// The LibraryTemplatePlugin stores the template result in a local variable.
// To extract the result during the evaluation this part has to be removed.
source = source.replace('var HTML_WEBPACK_PLUGIN_RESULT =', '');
const template = this.options.template.replace(/^.+!/, '').replace(/\?.+$/, '');
const vmContext = vm.createContext(_.extend({HTML_WEBPACK_PLUGIN: true, require: require}, global));
const vmScript = new vm.Script(source, {filename: template});
// Evaluate code and cast to string
let newSource;
try {
newSource = vmScript.runInContext(vmContext);
} catch (e) {
return Promise.reject(e);
}
if (typeof newSource === 'object' && newSource.__esModule && newSource.default) {
newSource = newSource.default;
}
return typeof newSource === 'string' || typeof newSource === 'function'
? Promise.resolve(newSource)
: Promise.reject('The loader "' + this.options.template + '" didn\'t return html.');
}
/**
* Generate the template parameters for the template function
*/
getTemplateParameters (compilation, assets) {
if (typeof this.options.templateParameters === 'function') {
return this.options.templateParameters(compilation, assets, this.options);
}
if (typeof this.options.templateParameters === 'object') {
return this.options.templateParameters;
}
return {};
}
/**
* Html post processing
*
* Returns a promise
*/
executeTemplate (templateFunction, chunks, assets, compilation) {
return Promise.resolve()
// Template processing
.then(() => {
const templateParams = this.getTemplateParameters(compilation, assets);
let html = '';
try {
html = templateFunction(templateParams);
} catch (e) {
compilation.errors.push(new Error('Template execution failed: ' + e));
return Promise.reject(e);
}
return html;
});
}
/**
* Html post processing
*
* Returns a promise
*/
postProcessHtml (html, assets, assetTags) {
const self = this;
if (typeof html !== 'string') {
return Promise.reject('Expected html to be a string but got ' + JSON.stringify(html));
}
return Promise.resolve()
// Inject
.then(() => {
if (self.options.inject) {
return self.injectAssetsIntoHtml(html, assets, assetTags);
} else {
return html;
}
})
// Minify
.then(html => {
if (self.options.minify) {
const minify = require('html-minifier').minify;
return minify(html, self.options.minify);
}
return html;
});
}
/*
* Pushes the content of the given filename to the compilation assets
*/
addFileToAssets (filename, compilation) {
filename = path.resolve(compilation.compiler.context, filename);
return Promise.all([
fsStatAsync(filename),
fsReadFileAsync(filename)
])
.then(([size, source]) => {
return {
size,
source
};
})
.catch(() => Promise.reject(new Error('HtmlWebpackPlugin: could not load file ' + filename)))
.then(results => {
const basename = path.basename(filename);
if (compilation.fileDependencies.add) {
compilation.fileDependencies.add(filename);
} else {
// Before Webpack 4 - fileDepenencies was an array
compilation.fileDependencies.push(filename);
}
compilation.assets[basename] = {
source: () => results.source,
size: () => results.size.size
};
return basename;
});
}
/**
* Helper to sort chunks
*/
sortChunks (chunks, sortMode, compilation) {
// Custom function
if (typeof sortMode === 'function') {
return chunks.sort(sortMode);
}
// Check if the given sort mode is a valid chunkSorter sort mode
if (typeof chunkSorter[sortMode] !== 'undefined') {
return chunkSorter[sortMode](chunks, this.options, compilation);
}
throw new Error('"' + sortMode + '" is not a valid chunk sort mode');
}
/**
* Return all chunks from the compilation result which match the exclude and include filters
*/
filterChunks (chunks, includedChunks, excludedChunks) {
return chunks.filter(chunk => {
const chunkName = chunk.names[0];
// This chunk doesn't have a name. This script can't handled it.
if (chunkName === undefined) {
return false;
}
// Skip if the chunk should be lazy loaded
if (typeof chunk.isInitial === 'function') {
if (!chunk.isInitial()) {
return false;
}
} else if (!chunk.initial) {
return false;
}
// Skip if the chunks should be filtered and the given chunk was not added explicity
if (Array.isArray(includedChunks) && includedChunks.indexOf(chunkName) === -1) {
return false;
}
// Skip if the chunks should be filtered and the given chunk was excluded explicity
if (Array.isArray(excludedChunks) && excludedChunks.indexOf(chunkName) !== -1) {
return false;
}
// Add otherwise
return true;
});
}
isHotUpdateCompilation (assets) {
return assets.js.length && assets.js.every(name => /\.hot-update\.js$/.test(name));
}
htmlWebpackPluginAssets (compilation, chunks) {
const self = this;
const compilationHash = compilation.hash;
// Use the configured public path or build a relative path
let publicPath = typeof compilation.options.output.publicPath !== 'undefined'
// If a hard coded public path exists use it
? compilation.mainTemplate.getPublicPath({hash: compilationHash})
// If no public path was set get a relative url path
: path.relative(path.resolve(compilation.options.output.path, path.dirname(self.childCompilationOutputName)), compilation.options.output.path)
.split(path.sep).join('/');
if (publicPath.length && publicPath.substr(-1, 1) !== '/') {
publicPath += '/';
}
const assets = {
// The public path
publicPath: publicPath,
// Will contain all js & css files by chunk
chunks: {},
// Will contain all js files
js: [],
// Will contain all css files
css: [],
// Will contain the html5 appcache manifest files if it exists
manifest: Object.keys(compilation.assets).filter(assetFile => path.extname(assetFile) === '.appcache')[0]
};
// Append a hash for cache busting
if (this.options.hash) {
assets.manifest = self.appendHash(assets.manifest, compilationHash);
assets.favicon = self.appendHash(assets.favicon, compilationHash);
}
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
const chunkName = chunk.names[0];
assets.chunks[chunkName] = {};
// Prepend the public path to all chunk files
let chunkFiles = [].concat(chunk.files).map(chunkFile => publicPath + chunkFile);
// Append a hash for cache busting
if (this.options.hash) {
chunkFiles = chunkFiles.map(chunkFile => self.appendHash(chunkFile, compilationHash));
}
// Webpack outputs an array for each chunk when using sourcemaps
// or when one chunk hosts js and css simultaneously
const js = chunkFiles.find(chunkFile => /.js($|\?)/.test(chunkFile));
if (js) {
assets.chunks[chunkName].size = chunk.size;
assets.chunks[chunkName].entry = js;
assets.chunks[chunkName].hash = chunk.hash;
assets.js.push(js);
}
// Gather all css files
const css = chunkFiles.filter(chunkFile => /.css($|\?)/.test(chunkFile));
assets.chunks[chunkName].css = css;
assets.css = assets.css.concat(css);
}
// Duplicate css assets can occur on occasion if more than one chunk
// requires the same css.
assets.css = _.uniq(assets.css);
return assets;
}
/**
* Generate meta tags
*/
getMetaTags () {
if (this.options.meta === false) {
return [];
}
// Make tags self-closing in case of xhtml
// Turn { "viewport" : "width=500, initial-scale=1" } into
// [{ name:"viewport" content:"width=500, initial-scale=1" }]
const selfClosingTag = !!this.options.xhtml;
const metaTagAttributeObjects = Object.keys(this.options.meta).map((metaName) => {
const metaTagContent = this.options.meta[metaName];
return (typeof metaTagContent === 'object') ? metaTagContent : {
name: metaName,
content: metaTagContent
};
});
// Turn [{ name:"viewport" content:"width=500, initial-scale=1" }] into
// the html-webpack-plugin tag structure
return metaTagAttributeObjects.map((metaTagAttributes) => {
return {
tagName: 'meta',
voidTag: true,
selfClosingTag: selfClosingTag,
attributes: metaTagAttributes
};
});
}
/**
* Injects the assets into the given html string
*/
generateHtmlTags (assets) {
// Turn script files into script tags
const scripts = assets.js.map(scriptPath => ({
tagName: 'script',
closeTag: true,
attributes: {
type: 'text/javascript',
src: scriptPath
}
}));
// Make tags self-closing in case of xhtml
const selfClosingTag = !!this.options.xhtml;
// Turn css files into link tags
const styles = assets.css.map(stylePath => ({
tagName: 'link',
selfClosingTag: selfClosingTag,
voidTag: true,
attributes: {
href: stylePath,
rel: 'stylesheet'
}
}));
// Injection targets
let head = this.getMetaTags();
let body = [];
// If there is a favicon present, add it to the head
if (assets.favicon) {
head.push({
tagName: 'link',
selfClosingTag: selfClosingTag,
voidTag: true,
attributes: {
rel: 'shortcut icon',
href: assets.favicon
}
});
}
// Add styles to the head
head = head.concat(styles);
// Add scripts to body or head
if (this.options.inject === 'head') {
head = head.concat(scripts);
} else {
body = body.concat(scripts);
}
return {head: head, body: body};
}
/**
* Injects the assets into the given html string
*/
injectAssetsIntoHtml (html, assets, assetTags) {
const htmlRegExp = /(<html[^>]*>)/i;
const headRegExp = /(<\/head\s*>)/i;
const bodyRegExp = /(<\/body\s*>)/i;
const body = assetTags.body.map(this.createHtmlTag.bind(this));
const head = assetTags.head.map(this.createHtmlTag.bind(this));
if (body.length) {
if (bodyRegExp.test(html)) {
// Append assets to body element
html = html.replace(bodyRegExp, match => body.join('') + match);
} else {
// Append scripts to the end of the file if no <body> element exists:
html += body.join('');
}
}
if (head.length) {
// Create a head tag if none exists
if (!headRegExp.test(html)) {
if (!htmlRegExp.test(html)) {
html = '<head></head>' + html;
} else {
html = html.replace(htmlRegExp, match => match + '<head></head>');
}
}
// Append assets to head element
html = html.replace(headRegExp, match => head.join('') + match);
}
// Inject manifest into the opening html tag
if (assets.manifest) {
html = html.replace(/(<html[^>]*)(>)/i, (match, start, end) => {
// Append the manifest only if no manifest was specified
if (/\smanifest\s*=/.test(match)) {
return match;
}
return start + ' manifest="' + assets.manifest + '"' + end;
});
}
return html;
}
/**
* Appends a cache busting hash
*/
appendHash (url, hash) {
if (!url) {
return url;
}
return url + (url.indexOf('?') === -1 ? '?' : '&') + hash;
}
/**
* Turn a tag definition into a html string
*/
createHtmlTag (tagDefinition) {
const attributes = Object.keys(tagDefinition.attributes || {})
.filter(attributeName => tagDefinition.attributes[attributeName] !== false)
.map(attributeName => {
if (tagDefinition.attributes[attributeName] === true) {
return attributeName;
}
return attributeName + '="' + tagDefinition.attributes[attributeName] + '"';
});
// Backport of 3.x void tag definition
const voidTag = tagDefinition.voidTag !== undefined ? tagDefinition.voidTag : !tagDefinition.closeTag;
const selfClosingTag = tagDefinition.voidTag !== undefined ? tagDefinition.voidTag && this.options.xhtml : tagDefinition.selfClosingTag;
return '<' + [tagDefinition.tagName].concat(attributes).join(' ') + (selfClosingTag ? '/' : '') + '>' +
(tagDefinition.innerHTML || '') +
(voidTag ? '' : '</' + tagDefinition.tagName + '>');
}
/**
* Helper to return the absolute template path with a fallback loader
*/
getFullTemplatePath (template, context) {
// If the template doesn't use a loader use the lodash template loader
if (template.indexOf('!') === -1) {
template = require.resolve('./lib/loader.js') + '!' + path.resolve(context, template);
}
// Resolve template path
return template.replace(
/([!])([^/\\][^!?]+|[^/\\!?])($|\?[^!?\n]+$)/,
(match, prefix, filepath, postfix) => prefix + path.resolve(filepath) + postfix);
}
/**
* Helper to return a sorted unique array of all asset files out of the
* asset object
*/
getAssetFiles (assets) {
const files = _.uniq(Object.keys(assets).filter(assetType => assetType !== 'chunks' && assets[assetType]).reduce((files, assetType) => files.concat(assets[assetType]), []));
files.sort();
return files;
}
/**
* Helper to promisify compilation.applyPluginsAsyncWaterfall that returns
* a function that helps to merge given plugin arguments with processed ones
*/
applyPluginsAsyncWaterfall (compilation) {
if (compilation.hooks) {
return (eventName, requiresResult, pluginArgs) => {
const ccEventName = trainCaseToCamelCase(eventName);
if (!compilation.hooks[ccEventName]) {
compilation.errors.push(
new Error('No hook found for ' + eventName)
);
}
return compilation.hooks[ccEventName].promise(pluginArgs);
};
}
// Before Webpack 4
const promisedApplyPluginsAsyncWaterfall = function (name, init) {
return new Promise((resolve, reject) => {
const callback = function (err, result) {
if (err) {
return reject(err);
}
resolve(result);
};
compilation.applyPluginsAsyncWaterfall(name, init, callback);
});
};
return (eventName, requiresResult, pluginArgs) => promisedApplyPluginsAsyncWaterfall(eventName, pluginArgs)
.then(result => {
if (requiresResult && !result) {
compilation.warnings.push(
new Error('Using ' + eventName + ' without returning a result is deprecated.')
);
}
return _.extend(pluginArgs, result);
});
}
}
/**
* Takes a string in train case and transforms it to camel case
*
* Example: 'hello-my-world' to 'helloMyWorld'
*
* @param {string} word
*/
function trainCaseToCamelCase (word) {
return word.replace(/-([\w])/g, (match, p1) => p1.toUpperCase());
}
/**
* The default for options.templateParameter
* Generate the template parameters
*/
function templateParametersGenerator (compilation, assets, options) {
return {
compilation: compilation,
webpack: compilation.getStats().toJson(),
webpackConfig: compilation.options,
htmlWebpackPlugin: {
files: assets,
options: options
}
};
}
module.exports = HtmlWebpackPlugin;
@@ -0,0 +1,139 @@
'use strict';
const toposort = require('toposort');
const _ = require('lodash');
/**
Sorts dependencies between chunks by their "parents" attribute.
This function sorts chunks based on their dependencies with each other.
The parent relation between chunks as generated by Webpack for each chunk
is used to define a directed (and hopefully acyclic) graph, which is then
topologically sorted in order to retrieve the correct order in which
chunks need to be embedded into HTML. A directed edge in this graph is
describing a "is parent of" relationship from a chunk to another (distinct)
chunk. Thus topological sorting orders chunks from bottom-layer chunks to
highest level chunks that use the lower-level chunks.
@param {Array} chunks an array of chunks as generated by the html-webpack-plugin.
- For webpack < 4, It is assumed that each entry contains at least the properties
"id" (containing the chunk id) and "parents" (array containing the ids of the
parent chunks).
- For webpack 4+ the see the chunkGroups param for parent-child relationships
@param {Array} chunks an array of ChunkGroups that has a getParents method.
Each ChunkGroup contains a list of chunks in order.
@return {Array} A topologically sorted version of the input chunks
*/
module.exports.dependency = (chunks, options, compilation) => {
const chunkGroups = compilation.chunkGroups;
if (!chunks) {
return chunks;
}
// We build a map (chunk-id -> chunk) for faster access during graph building.
const nodeMap = {};
chunks.forEach(chunk => {
nodeMap[chunk.id] = chunk;
});
// Next, we add an edge for each parent relationship into the graph
let edges = [];
if (chunkGroups) {
// Add an edge for each parent (parent -> child)
edges = chunkGroups.reduce((result, chunkGroup) => result.concat(
Array.from(chunkGroup.parentsIterable, parentGroup => [parentGroup, chunkGroup])
), []);
const sortedGroups = toposort.array(chunkGroups, edges);
// flatten chunkGroup into chunks
const sortedChunks = sortedGroups
.reduce((result, chunkGroup) => result.concat(chunkGroup.chunks), [])
.map(chunk => // use the chunk from the list passed in, since it may be a filtered list
nodeMap[chunk.id])
.filter((chunk, index, self) => {
// make sure exists (ie excluded chunks not in nodeMap)
const exists = !!chunk;
// make sure we have a unique list
const unique = self.indexOf(chunk) === index;
return exists && unique;
});
return sortedChunks;
} else {
// before webpack 4 there was no chunkGroups
chunks.forEach(chunk => {
if (chunk.parents) {
// Add an edge for each parent (parent -> child)
chunk.parents.forEach(parentId => {
// webpack2 chunk.parents are chunks instead of string id(s)
const parentChunk = _.isObject(parentId) ? parentId : nodeMap[parentId];
// If the parent chunk does not exist (e.g. because of an excluded chunk)
// we ignore that parent
if (parentChunk) {
edges.push([parentChunk, chunk]);
}
});
}
});
// We now perform a topological sorting on the input chunks and built edges
return toposort.array(chunks, edges);
}
};
/**
* Sorts the chunks based on the chunk id.
*
* @param {Array} chunks the list of chunks to sort
* @return {Array} The sorted list of chunks
*/
module.exports.id = chunks => chunks.sort(function orderEntryLast (a, b) {
if (a.entry !== b.entry) {
return b.entry ? 1 : -1;
} else {
return b.id - a.id;
}
});
/**
* Performs identity mapping (no-sort).
* @param {Array} chunks the chunks to sort
* @return {Array} The sorted chunks
*/
module.exports.none = chunks => chunks;
/**
* Sort manually by the chunks
* @param {Array} chunks the chunks to sort
* @return {Array} The sorted chunks
*/
module.exports.manual = (chunks, options) => {
const specifyChunks = options.chunks;
const chunksResult = [];
let filterResult = [];
if (Array.isArray(specifyChunks)) {
for (var i = 0; i < specifyChunks.length; i++) {
filterResult = chunks.filter(chunk => {
if (chunk.names[0] && chunk.names[0] === specifyChunks[i]) {
return true;
}
return false;
});
filterResult.length > 0 && chunksResult.push(filterResult[0]);
}
}
return chunksResult;
};
/**
* Defines the default sorter.
*/
module.exports.auto = module.exports.id;
// In webpack 2 the ids have been flipped.
// Therefore the id sort doesn't work the same way as it did for webpack 1
// Luckily the dependency sort is working as expected
if (Number(require('webpack/package.json').version.split('.')[0]) > 1) {
module.exports.auto = module.exports.dependency;
}
@@ -0,0 +1,125 @@
/*
* This file uses webpack to compile a template with a child compiler.
*
* [TEMPLATE] -> [JAVASCRIPT]
*
*/
'use strict';
const path = require('path');
const NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin');
const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
const LoaderTargetPlugin = require('webpack/lib/LoaderTargetPlugin');
const LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin');
const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
/**
* Compiles the template into a nodejs factory, adds its to the compilation.assets
* and returns a promise of the result asset object.
*
* @param template relative path to the template file
* @param context path context
* @param outputFilename the file name
* @param compilation The webpack compilation object
*
* Returns an object:
* {
* hash: {String} - Base64 hash of the file
* content: {String} - Javascript executable code of the template
* }
*
*/
module.exports.compileTemplate = function compileTemplate (template, context, outputFilename, compilation) {
// The entry file is just an empty helper as the dynamic template
// require is added in "loader.js"
const outputOptions = {
filename: outputFilename,
publicPath: compilation.outputOptions.publicPath
};
// Store the result of the parent compilation before we start the child compilation
const assetsBeforeCompilation = Object.assign({}, compilation.assets[outputOptions.filename]);
// Create an additional child compiler which takes the template
// and turns it into an Node.JS html factory.
// This allows us to use loaders during the compilation
const compilerName = getCompilerName(context, outputFilename);
const childCompiler = compilation.createChildCompiler(compilerName, outputOptions);
childCompiler.context = context;
new NodeTemplatePlugin(outputOptions).apply(childCompiler);
new NodeTargetPlugin().apply(childCompiler);
new LibraryTemplatePlugin('HTML_WEBPACK_PLUGIN_RESULT', 'var').apply(childCompiler);
// Using undefined as name for the SingleEntryPlugin causes a unexpected output as described in
// https://github.com/jantimon/html-webpack-plugin/issues/895
// Using a string as a name for the SingleEntryPlugin causes problems with HMR as described in
// https://github.com/jantimon/html-webpack-plugin/issues/900
// Until the HMR issue is fixed we keep the ugly output:
new SingleEntryPlugin(this.context, template, undefined).apply(childCompiler);
new LoaderTargetPlugin('node').apply(childCompiler);
// Fix for "Uncaught TypeError: __webpack_require__(...) is not a function"
// Hot module replacement requires that every child compiler has its own
// cache. @see https://github.com/ampedandwired/html-webpack-plugin/pull/179
// Backwards compatible version of: childCompiler.hooks.compilation
(childCompiler.hooks ? childCompiler.hooks.compilation.tap.bind(childCompiler.hooks.compilation, 'HtmlWebpackPlugin') : childCompiler.plugin.bind(childCompiler, 'compilation'))(compilation => {
if (compilation.cache) {
if (!compilation.cache[compilerName]) {
compilation.cache[compilerName] = {};
}
compilation.cache = compilation.cache[compilerName];
}
});
// Compile and return a promise
return new Promise((resolve, reject) => {
childCompiler.runAsChild((err, entries, childCompilation) => {
// Resolve / reject the promise
if (childCompilation && childCompilation.errors && childCompilation.errors.length) {
const errorDetails = childCompilation.errors.map(error => error.message + (error.error ? ':\n' + error.error : '')).join('\n');
reject(new Error('Child compilation failed:\n' + errorDetails));
} else if (err) {
reject(err);
} else {
// Replace [hash] placeholders in filename
// In webpack 4 the plugin interface changed, so check for available fns
const outputName = compilation.mainTemplate.getAssetPath
? compilation.mainTemplate.hooks.assetPath.call(outputOptions.filename, {
hash: childCompilation.hash,
chunk: entries[0]
})
: compilation.mainTemplate.applyPluginsWaterfall(
'asset-path',
outputOptions.filename,
{
hash: childCompilation.hash,
chunk: entries[0]
});
// Restore the parent compilation to the state like it
// was before the child compilation
compilation.assets[outputName] = assetsBeforeCompilation[outputName];
if (assetsBeforeCompilation[outputName] === undefined) {
// If it wasn't there - delete it
delete compilation.assets[outputName];
}
resolve({
// Hash of the template entry point
hash: entries[0].hash,
// Output name
outputName: outputName,
// Compiled code
content: childCompilation.assets[outputName].source()
});
}
});
});
};
/**
* Returns the child compiler name e.g. 'html-webpack-plugin for "index.html"'
*/
function getCompilerName (context, filename) {
const absolutePath = path.resolve(context, filename);
const relativePath = path.relative(context, absolutePath);
return 'html-webpack-plugin for "' + (absolutePath.length < relativePath.length ? absolutePath : relativePath) + '"';
}
@@ -0,0 +1,23 @@
'use strict';
const PrettyError = require('pretty-error');
const prettyError = new PrettyError();
prettyError.withoutColors();
prettyError.skipPackage(['html-plugin-evaluation']);
prettyError.skipNodeFiles();
prettyError.skip(function (traceLine) {
return traceLine.path === 'html-plugin-evaluation';
});
module.exports = function (err, context) {
return {
toHtml: function () {
return 'Html Webpack Plugin:\n<pre>\n' + this.toString() + '</pre>';
},
toJsonHtml: function () {
return JSON.stringify(this.toHtml());
},
toString: function () {
return prettyError.render(err).replace(/webpack:\/\/\/\./g, context);
}
};
};
@@ -0,0 +1,35 @@
/* This loader renders the template with underscore if no other loader was found */
'use strict';
const _ = require('lodash');
const loaderUtils = require('loader-utils');
module.exports = function (source) {
if (this.cacheable) {
this.cacheable();
}
const allLoadersButThisOne = this.loaders.filter(function (loader) {
// Loader API changed from `loader.module` to `loader.normal` in Webpack 2.
return (loader.module || loader.normal) !== module.exports;
});
// This loader shouldn't kick in if there is any other loader
if (allLoadersButThisOne.length > 0) {
return source;
}
// Skip .js files
if (/\.js$/.test(this.resourcePath)) {
return source;
}
// The following part renders the tempalte with lodash as aminimalistic loader
//
// Get templating options
const options = this.query !== '' ? loaderUtils.parseQuery(this.query) : {};
const template = _.template(source, _.defaults(options, { variable: 'data' }));
// Require !!lodash - using !! will disable all loaders (e.g. babel)
return 'var _ = require(' + loaderUtils.stringifyRequest(this, '!!' + require.resolve('lodash')) + ');' +
'module.exports = function (templateParams) { with(templateParams) {' +
// Execute the lodash template
'return (' + template.source + ')();' +
'}}';
};
@@ -0,0 +1,77 @@
{
"name": "html-webpack-plugin",
"version": "3.2.0",
"license": "MIT",
"description": "Simplifies creation of HTML files to serve your webpack bundles",
"author": "Charles Blaxland <charles.blaxland@gmail.com> (https://github.com/ampedandwired)",
"main": "index.js",
"files": [
"lib/",
"index.js",
"default_index.ejs"
],
"scripts": {
"pretest": "semistandard",
"commit": "git-cz",
"build-examples": "node examples/build-examples.js",
"test": "jasmine",
"release": "standard-version"
},
"semistandard": {
"ignore": [
"examples/*/dist/**/*.*"
]
},
"devDependencies": {
"appcache-webpack-plugin": "^1.3.0",
"commitizen": "2.9.6",
"css-loader": "^0.26.1",
"cz-conventional-changelog": "2.1.0",
"dir-compare": "1.3.0",
"es6-promise": "^4.0.5",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.9.0",
"html-loader": "^0.4.4",
"jade": "^1.11.0",
"jade-loader": "^0.8.0",
"jasmine": "^2.5.2",
"jasmine-diff-matchers": "^2.0.0",
"rimraf": "^2.5.4",
"semistandard": "8.0.0",
"standard-version": "^4.3.0",
"style-loader": "^0.13.1",
"underscore-template-loader": "^0.7.3",
"url-loader": "^0.5.7",
"webpack": "^1.14.0",
"webpack-recompilation-simulator": "^1.3.0"
},
"dependencies": {
"html-minifier": "^3.2.3",
"loader-utils": "^0.2.16",
"lodash": "^4.17.3",
"pretty-error": "^2.0.2",
"tapable": "^1.0.0",
"toposort": "^1.0.0",
"util.promisify": "1.0.0"
},
"peerDependencies": {
"webpack": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0"
},
"keywords": [
"webpack",
"plugin",
"html",
"html-webpack-plugin"
],
"bugs": "https://github.com/jantimon/html-webpack-plugin/issues",
"homepage": "https://github.com/jantimon/html-webpack-plugin",
"repository": "https://github.com/jantimon/html-webpack-plugin.git",
"engines": {
"node": ">=6.9"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}
+169
View File
@@ -0,0 +1,169 @@
### v0.5.1 [[code][c0.5.1], [diff][d0.5.1]]
[c0.5.1]: https://github.com/aseemk/json5/tree/v0.5.1
[d0.5.1]: https://github.com/aseemk/json5/compare/v0.5.0...v0.5.1
This release includes a minor fix for indentations when stringifying empty
arrays.
- Fix: Indents no longer appear in empty arrays when stringified. ([#134])
### v0.5.0 [[code][c0.5.0], [diff][d0.5.0]]
[c0.5.0]: https://github.com/aseemk/json5/tree/v0.5.0
[d0.5.0]: https://github.com/aseemk/json5/compare/v0.4.0...v0.5.0
This release includes major internal changes and public API enhancements.
- **Major:** JSON5 officially supports Node.js v4 LTS and v5. Support for
Node.js v0.6 and v0.8 have been dropped, while support for v0.10 and v0.12
remain.
- Fix: YUI Compressor no longer fails when compressing json5.js. ([#97])
- New: `parse` and the CLI provide line and column numbers when displaying error
messages. ([#101]; awesome work by [@amb26].)
### v0.4.0 [[code][c0.4.0], [diff][d0.4.0]]
[c0.4.0]: https://github.com/aseemk/json5/tree/v0.4.0
[d0.4.0]: https://github.com/aseemk/json5/compare/v0.2.0...v0.4.0
Note that v0.3.0 was tagged, but never published to npm, so this v0.4.0
changelog entry includes v0.3.0 features.
This is a massive release that adds `stringify` support, among other things.
- **Major:** `JSON5.stringify()` now exists!
This method is analogous to the native `JSON.stringify()`;
it just avoids quoting keys where possible.
See the [usage documentation](./README.md#usage) for more.
([#32]; huge thanks and props [@aeisenberg]!)
- New: `NaN` and `-NaN` are now allowed number literals.
([#30]; thanks [@rowanhill].)
- New: Duplicate object keys are now allowed; the last value is used.
This is the same behavior as JSON. ([#57]; thanks [@jordanbtucker].)
- Fix: Properly handle various whitespace and newline cases now.
E.g. JSON5 now properly supports escaped CR and CRLF newlines in strings,
and JSON5 now accepts the same whitespace as JSON (stricter than ES5).
([#58], [#60], and [#63]; thanks [@jordanbtucker].)
- New: Negative hexadecimal numbers (e.g. `-0xC8`) are allowed again.
(They were disallowed in v0.2.0; see below.)
It turns out they *are* valid in ES5, so JSON5 supports them now too.
([#36]; thanks [@jordanbtucker]!)
### v0.2.0 [[code][c0.2.0], [diff][d0.2.0]]
[c0.2.0]: https://github.com/aseemk/json5/tree/v0.2.0
[d0.2.0]: https://github.com/aseemk/json5/compare/v0.1.0...v0.2.0
This release fixes some bugs and adds some more utility features to help you
express data more easily:
- **Breaking:** Negative hexadecimal numbers (e.g. `-0xC8`) are rejected now.
While V8 (e.g. Chrome and Node) supported them, it turns out they're invalid
in ES5. This has been [fixed in V8][v8-hex-fix] (and by extension, Chrome
and Node), so JSON5 officially rejects them now, too. ([#36])
- New: Trailing decimal points in decimal numbers are allowed again.
(They were disallowed in v0.1.0; see below.)
They're allowed by ES5, and differentiating between integers and floats may
make sense on some platforms. ([#16]; thanks [@Midar].)
- New: `Infinity` and `-Infinity` are now allowed number literals.
([#30]; thanks [@pepkin88].)
- New: Plus signs (`+`) in front of numbers are now allowed, since it can
be helpful in some contexts to explicitly mark numbers as positive.
(E.g. when a property represents changes or deltas.)
- Fix: unescaped newlines in strings are rejected now.
([#24]; thanks [@Midar].)
### v0.1.0 [[code][c0.1.0], [diff][d0.1.0]]
[c0.1.0]: https://github.com/aseemk/json5/tree/v0.1.0
[d0.1.0]: https://github.com/aseemk/json5/compare/v0.0.1...v0.1.0
This release tightens JSON5 support and adds helpful utility features:
- New: Support hexadecimal numbers. (Thanks [@MaxNanasy].)
- Fix: Reject octal numbers properly now. Previously, they were accepted but
improperly parsed as base-10 numbers. (Thanks [@MaxNanasy].)
- **Breaking:** Reject "noctal" numbers now (base-10 numbers that begin with a
leading zero). These are disallowed by both JSON5 and JSON, as well as by
ES5's strict mode. (Thanks [@MaxNanasy].)
- New: Support leading decimal points in decimal numbers.
(Thanks [@MaxNanasy].)
- **Breaking:** Reject trailing decimal points in decimal numbers now. These
are disallowed by both JSON5 and JSON. (Thanks [@MaxNanasy].)
- **Breaking:** Reject omitted elements in arrays now. These are disallowed by
both JSON5 and JSON.
- Fix: Throw proper `SyntaxError` instances on errors now.
- New: Add Node.js `require()` hook. Register via `json5/lib/require`.
- New: Add Node.js `json5` executable to compile JSON5 files to JSON.
### v0.0.1 [[code][c0.0.1], [diff][d0.0.1]]
[c0.0.1]: https://github.com/aseemk/json5/tree/v0.0.1
[d0.0.1]: https://github.com/aseemk/json5/compare/v0.0.0...v0.0.1
This was the first implementation of this JSON5 parser.
- Support unquoted object keys, including reserved words. Unicode characters
and escape sequences sequences aren't yet supported.
- Support single-quoted strings.
- Support multi-line strings.
- Support trailing commas in arrays and objects.
- Support comments, both inline and block.
### v0.0.0 [[code](https://github.com/aseemk/json5/tree/v0.0.0)]
Let's consider this to be Douglas Crockford's original [json_parse.js] — a
parser for the regular JSON format.
[json_parse.js]: https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js
[v8-hex-fix]: http://code.google.com/p/v8/issues/detail?id=2240
[@MaxNanasy]: https://github.com/MaxNanasy
[@Midar]: https://github.com/Midar
[@pepkin88]: https://github.com/pepkin88
[@rowanhill]: https://github.com/rowanhill
[@aeisenberg]: https://github.com/aeisenberg
[@jordanbtucker]: https://github.com/jordanbtucker
[@amb26]: https://github.com/amb26
[#16]: https://github.com/aseemk/json5/issues/16
[#24]: https://github.com/aseemk/json5/issues/24
[#30]: https://github.com/aseemk/json5/issues/30
[#32]: https://github.com/aseemk/json5/issues/32
[#36]: https://github.com/aseemk/json5/issues/36
[#57]: https://github.com/aseemk/json5/issues/57
[#58]: https://github.com/aseemk/json5/pull/58
[#60]: https://github.com/aseemk/json5/pull/60
[#63]: https://github.com/aseemk/json5/pull/63
[#97]: https://github.com/aseemk/json5/pull/97
[#101]: https://github.com/aseemk/json5/pull/101
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2012-2016 Aseem Kishore, and [others](https://github.com/aseemk/json5/contributors).
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+286
View File
@@ -0,0 +1,286 @@
# JSON5 – Modern JSON
[![Build Status](https://travis-ci.org/json5/json5.svg)](https://travis-ci.org/json5/json5)
JSON is an excellent data format, but we think it can be better.
**JSON5 is a proposed extension to JSON** that aims to make it easier for
*humans to write and maintain* by hand. It does this by adding some minimal
syntax features directly from ECMAScript 5.
JSON5 remains a **strict subset of JavaScript**, adds **no new data types**,
and **works with all existing JSON content**.
JSON5 is *not* an official successor to JSON, and JSON5 content may *not*
work with existing JSON parsers. For this reason, JSON5 files use a new .json5
extension. *(TODO: new MIME type needed too.)*
The code here is a **reference JavaScript implementation** for both Node.js
and all browsers. It’s based directly off of Douglas Crockford’s own [JSON
implementation][json_parse.js], and it’s both robust and secure.
## Why
JSON isn’t the friendliest to *write*. Keys need to be quoted, objects and
arrays can’t have trailing commas, and comments aren’t allowed — even though
none of these are the case with regular JavaScript today.
That was fine when JSON’s goal was to be a great data format, but JSON’s usage
has expanded beyond *machines*. JSON is now used for writing [configs][ex1],
[manifests][ex2], even [tests][ex3] — all by *humans*.
[ex1]: http://plovr.com/docs.html
[ex2]: https://www.npmjs.org/doc/files/package.json.html
[ex3]: http://code.google.com/p/fuzztester/wiki/JSONFileFormat
There are other formats that are human-friendlier, like YAML, but changing
from JSON to a completely different format is undesirable in many cases.
JSON5’s aim is to remain close to JSON and JavaScript.
## Features
The following is the exact list of additions to JSON’s syntax introduced by
JSON5. **All of these are optional**, and **all of these come from ES5**.
### Objects
- Object keys can be unquoted if they’re valid [identifiers][mdn_variables].
Yes, even reserved keywords (like `default`) are valid unquoted keys in ES5
[[§11.1.5](http://es5.github.com/#x11.1.5), [§7.6](http://es5.github.com/#x7.6)].
([More info](https://mathiasbynens.be/notes/javascript-identifiers))
*(TODO: Unicode characters and escape sequences aren’t yet supported in this
implementation.)*
- Object keys can also be single-quoted.
- Objects can have trailing commas.
[mdn_variables]: https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Core_Language_Features#Variables
### Arrays
- Arrays can have trailing commas.
### Strings
- Strings can be single-quoted.
- Strings can be split across multiple lines; just prefix each newline with a
backslash. [ES5 [§7.8.4](http://es5.github.com/#x7.8.4)]
### Numbers
- Numbers can be hexadecimal (base 16).
- Numbers can begin or end with a (leading or trailing) decimal point.
- Numbers can include `Infinity`, `-Infinity`, `NaN`, and `-NaN`.
- Numbers can begin with an explicit plus sign.
### Comments
- Both inline (single-line) and block (multi-line) comments are allowed.
## Example
The following is a contrived example, but it illustrates most of the features:
```js
{
foo: 'bar',
while: true,
this: 'is a \
multi-line string',
// this is an inline comment
here: 'is another', // inline comment
/* this is a block comment
that continues on another line */
hex: 0xDEADbeef,
half: .5,
delta: +10,
to: Infinity, // and beyond!
finally: 'a trailing comma',
oh: [
"we shouldn't forget",
'arrays can have',
'trailing commas too',
],
}
```
This implementation’s own [package.json5](package.json5) is more realistic:
```js
// This file is written in JSON5 syntax, naturally, but npm needs a regular
// JSON file, so compile via `npm run build`. Be sure to keep both in sync!
{
name: 'json5',
version: '0.5.0',
description: 'JSON for the ES5 era.',
keywords: ['json', 'es5'],
author: 'Aseem Kishore <aseem.kishore@gmail.com>',
contributors: [
// TODO: Should we remove this section in favor of GitHub's list?
// https://github.com/aseemk/json5/contributors
'Max Nanasy <max.nanasy@gmail.com>',
'Andrew Eisenberg <andrew@eisenberg.as>',
'Jordan Tucker <jordanbtucker@gmail.com>',
],
main: 'lib/json5.js',
bin: 'lib/cli.js',
files: ["lib/"],
dependencies: {},
devDependencies: {
gulp: "^3.9.1",
'gulp-jshint': "^2.0.0",
jshint: "^2.9.1",
'jshint-stylish': "^2.1.0",
mocha: "^2.4.5"
},
scripts: {
build: 'node ./lib/cli.js -c package.json5',
test: 'mocha --ui exports --reporter spec',
// TODO: Would it be better to define these in a mocha.opts file?
},
homepage: 'http://json5.org/',
license: 'MIT',
repository: {
type: 'git',
url: 'https://github.com/aseemk/json5.git',
},
}
```
## Community
Join the [Google Group](http://groups.google.com/group/json5) if you’re
interested in JSON5 news, updates, and general discussion.
Don’t worry, it’s very low-traffic.
The [GitHub wiki](https://github.com/aseemk/json5/wiki) is a good place to track
JSON5 support and usage. Contribute freely there!
[GitHub Issues](https://github.com/aseemk/json5/issues) is the place to
formally propose feature requests and report bugs. Questions and general
feedback are better directed at the Google Group.
## Usage
This JavaScript implementation of JSON5 simply provides a `JSON5` object just
like the native ES5 `JSON` object.
To use from Node:
```sh
npm install json5
```
```js
var JSON5 = require('json5');
```
To use in the browser (adds the `JSON5` object to the global namespace):
```html
<script src="json5.js"></script>
```
Then in both cases, you can simply replace native `JSON` calls with `JSON5`:
```js
var obj = JSON5.parse('{unquoted:"key",trailing:"comma",}');
var str = JSON5.stringify(obj);
```
`JSON5.parse` supports all of the JSON5 features listed above (*TODO: except
Unicode*), as well as the native [`reviver` argument][json-parse].
[json-parse]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
`JSON5.stringify` mainly avoids quoting keys where possible, but we hope to
keep expanding it in the future (e.g. to also output trailing commas).
It supports the native [`replacer` and `space` arguments][json-stringify],
as well. *(TODO: Any implemented `toJSON` methods aren’t used today.)*
[json-stringify]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
### Extras
If you’re running this on Node, you can also register a JSON5 `require()` hook
to let you `require()` `.json5` files just like you can `.json` files:
```js
require('json5/lib/require');
require('./path/to/foo'); // tries foo.json5 after foo.js, foo.json, etc.
require('./path/to/bar.json5');
```
This module also provides a `json5` executable (requires Node) for converting
JSON5 files to JSON:
```sh
json5 -c path/to/foo.json5 # generates path/to/foo.json
```
## Development
```sh
git clone git://github.com/aseemk/json5.git
cd json5
npm install
npm test
```
As the `package.json5` file states, be sure to run `npm run build` on changes
to `package.json5`, since npm requires `package.json`.
Feel free to [file issues](https://github.com/aseemk/json5/issues) and submit
[pull requests](https://github.com/aseemk/json5/pulls) — contributions are
welcome. If you do submit a pull request, please be sure to add or update the
tests, and ensure that `npm test` continues to pass.
## License
MIT. See [LICENSE.md](./LICENSE.md) for details.
## Credits
[Michael Bolin](http://bolinfest.com/) independently arrived at and published
some of these same ideas with awesome explanations and detail.
Recommended reading:
[Suggested Improvements to JSON](http://bolinfest.com/essays/json.html)
[Douglas Crockford](http://www.crockford.com/) of course designed and built
JSON, but his state machine diagrams on the [JSON website](http://json.org/),
as cheesy as it may sound, gave me motivation and confidence that building a
new parser to implement these ideas this was within my reach!
This code is also modeled directly off of Doug’s open-source [json_parse.js][]
parser. I’m super grateful for that clean and well-documented code.
[json_parse.js]: https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js
[Max Nanasy](https://github.com/MaxNanasy) has been an early and prolific
supporter, contributing multiple patches and ideas. Thanks Max!
[Andrew Eisenberg](https://github.com/aeisenberg) has contributed the
`stringify` method.
[Jordan Tucker](https://github.com/jordanbtucker) has aligned JSON5 more closely
with ES5 and is actively maintaining this project.
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env node
// cli.js
// JSON5 command-line interface.
//
// This is pretty minimal for now; just supports compiling files via `-c`.
// TODO More useful functionality, like output path, watch, etc.?
var FS = require('fs');
var JSON5 = require('./json5');
var Path = require('path');
var USAGE = [
'Usage: json5 -c path/to/file.json5 ...',
'Compiles JSON5 files into sibling JSON files with the same basenames.',
].join('\n');
// if valid, args look like [node, json5, -c, file1, file2, ...]
var args = process.argv;
if (args.length < 4 || args[2] !== '-c') {
console.error(USAGE);
process.exit(1);
}
var cwd = process.cwd();
var files = args.slice(3);
// iterate over each file and convert JSON5 files to JSON:
files.forEach(function (file) {
var path = Path.resolve(cwd, file);
var basename = Path.basename(path, '.json5');
var dirname = Path.dirname(path);
var json5 = FS.readFileSync(path, 'utf8');
var obj = JSON5.parse(json5);
var json = JSON.stringify(obj, null, 4); // 4 spaces; TODO configurable?
path = Path.join(dirname, basename + '.json');
FS.writeFileSync(path, json, 'utf8');
});
+770
View File
@@ -0,0 +1,770 @@
// json5.js
// Modern JSON. See README.md for details.
//
// This file is based directly off of Douglas Crockford's json_parse.js:
// https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js
var JSON5 = (typeof exports === 'object' ? exports : {});
JSON5.parse = (function () {
"use strict";
// This is a function that can parse a JSON5 text, producing a JavaScript
// data structure. It is a simple, recursive descent parser. It does not use
// eval or regular expressions, so it can be used as a model for implementing
// a JSON5 parser in other languages.
// We are defining the function inside of another function to avoid creating
// global variables.
var at, // The index of the current character
lineNumber, // The current line number
columnNumber, // The current column number
ch, // The current character
escapee = {
"'": "'",
'"': '"',
'\\': '\\',
'/': '/',
'\n': '', // Replace escaped newlines in strings w/ empty string
b: '\b',
f: '\f',
n: '\n',
r: '\r',
t: '\t'
},
ws = [
' ',
'\t',
'\r',
'\n',
'\v',
'\f',
'\xA0',
'\uFEFF'
],
text,
renderChar = function (chr) {
return chr === '' ? 'EOF' : "'" + chr + "'";
},
error = function (m) {
// Call error when something is wrong.
var error = new SyntaxError();
// beginning of message suffix to agree with that provided by Gecko - see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
error.message = m + " at line " + lineNumber + " column " + columnNumber + " of the JSON5 data. Still to read: " + JSON.stringify(text.substring(at - 1, at + 19));
error.at = at;
// These two property names have been chosen to agree with the ones in Gecko, the only popular
// environment which seems to supply this info on JSON.parse
error.lineNumber = lineNumber;
error.columnNumber = columnNumber;
throw error;
},
next = function (c) {
// If a c parameter is provided, verify that it matches the current character.
if (c && c !== ch) {
error("Expected " + renderChar(c) + " instead of " + renderChar(ch));
}
// Get the next character. When there are no more characters,
// return the empty string.
ch = text.charAt(at);
at++;
columnNumber++;
if (ch === '\n' || ch === '\r' && peek() !== '\n') {
lineNumber++;
columnNumber = 0;
}
return ch;
},
peek = function () {
// Get the next character without consuming it or
// assigning it to the ch varaible.
return text.charAt(at);
},
identifier = function () {
// Parse an identifier. Normally, reserved words are disallowed here, but we
// only use this for unquoted object keys, where reserved words are allowed,
// so we don't check for those here. References:
// - http://es5.github.com/#x7.6
// - https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Core_Language_Features#Variables
// - http://docstore.mik.ua/orelly/webprog/jscript/ch02_07.htm
// TODO Identifiers can have Unicode "letters" in them; add support for those.
var key = ch;
// Identifiers must start with a letter, _ or $.
if ((ch !== '_' && ch !== '$') &&
(ch < 'a' || ch > 'z') &&
(ch < 'A' || ch > 'Z')) {
error("Bad identifier as unquoted key");
}
// Subsequent characters can contain digits.
while (next() && (
ch === '_' || ch === '$' ||
(ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9'))) {
key += ch;
}
return key;
},
number = function () {
// Parse a number value.
var number,
sign = '',
string = '',
base = 10;
if (ch === '-' || ch === '+') {
sign = ch;
next(ch);
}
// support for Infinity (could tweak to allow other words):
if (ch === 'I') {
number = word();
if (typeof number !== 'number' || isNaN(number)) {
error('Unexpected word for number');
}
return (sign === '-') ? -number : number;
}
// support for NaN
if (ch === 'N' ) {
number = word();
if (!isNaN(number)) {
error('expected word to be NaN');
}
// ignore sign as -NaN also is NaN
return number;
}
if (ch === '0') {
string += ch;
next();
if (ch === 'x' || ch === 'X') {
string += ch;
next();
base = 16;
} else if (ch >= '0' && ch <= '9') {
error('Octal literal');
}
}
switch (base) {
case 10:
while (ch >= '0' && ch <= '9' ) {
string += ch;
next();
}
if (ch === '.') {
string += '.';
while (next() && ch >= '0' && ch <= '9') {
string += ch;
}
}
if (ch === 'e' || ch === 'E') {
string += ch;
next();
if (ch === '-' || ch === '+') {
string += ch;
next();
}
while (ch >= '0' && ch <= '9') {
string += ch;
next();
}
}
break;
case 16:
while (ch >= '0' && ch <= '9' || ch >= 'A' && ch <= 'F' || ch >= 'a' && ch <= 'f') {
string += ch;
next();
}
break;
}
if(sign === '-') {
number = -string;
} else {
number = +string;
}
if (!isFinite(number)) {
error("Bad number");
} else {
return number;
}
},
string = function () {
// Parse a string value.
var hex,
i,
string = '',
delim, // double quote or single quote
uffff;
// When parsing for string values, we must look for ' or " and \ characters.
if (ch === '"' || ch === "'") {
delim = ch;
while (next()) {
if (ch === delim) {
next();
return string;
} else if (ch === '\\') {
next();
if (ch === 'u') {
uffff = 0;
for (i = 0; i < 4; i += 1) {
hex = parseInt(next(), 16);
if (!isFinite(hex)) {
break;
}
uffff = uffff * 16 + hex;
}
string += String.fromCharCode(uffff);
} else if (ch === '\r') {
if (peek() === '\n') {
next();
}
} else if (typeof escapee[ch] === 'string') {
string += escapee[ch];
} else {
break;
}
} else if (ch === '\n') {
// unescaped newlines are invalid; see:
// https://github.com/aseemk/json5/issues/24
// TODO this feels special-cased; are there other
// invalid unescaped chars?
break;
} else {
string += ch;
}
}
}
error("Bad string");
},
inlineComment = function () {
// Skip an inline comment, assuming this is one. The current character should
// be the second / character in the // pair that begins this inline comment.
// To finish the inline comment, we look for a newline or the end of the text.
if (ch !== '/') {
error("Not an inline comment");
}
do {
next();
if (ch === '\n' || ch === '\r') {
next();
return;
}
} while (ch);
},
blockComment = function () {
// Skip a block comment, assuming this is one. The current character should be
// the * character in the /* pair that begins this block comment.
// To finish the block comment, we look for an ending */ pair of characters,
// but we also watch for the end of text before the comment is terminated.
if (ch !== '*') {
error("Not a block comment");
}
do {
next();
while (ch === '*') {
next('*');
if (ch === '/') {
next('/');
return;
}
}
} while (ch);
error("Unterminated block comment");
},
comment = function () {
// Skip a comment, whether inline or block-level, assuming this is one.
// Comments always begin with a / character.
if (ch !== '/') {
error("Not a comment");
}
next('/');
if (ch === '/') {
inlineComment();
} else if (ch === '*') {
blockComment();
} else {
error("Unrecognized comment");
}
},
white = function () {
// Skip whitespace and comments.
// Note that we're detecting comments by only a single / character.
// This works since regular expressions are not valid JSON(5), but this will
// break if there are other valid values that begin with a / character!
while (ch) {
if (ch === '/') {
comment();
} else if (ws.indexOf(ch) >= 0) {
next();
} else {
return;
}
}
},
word = function () {
// true, false, or null.
switch (ch) {
case 't':
next('t');
next('r');
next('u');
next('e');
return true;
case 'f':
next('f');
next('a');
next('l');
next('s');
next('e');
return false;
case 'n':
next('n');
next('u');
next('l');
next('l');
return null;
case 'I':
next('I');
next('n');
next('f');
next('i');
next('n');
next('i');
next('t');
next('y');
return Infinity;
case 'N':
next( 'N' );
next( 'a' );
next( 'N' );
return NaN;
}
error("Unexpected " + renderChar(ch));
},
value, // Place holder for the value function.
array = function () {
// Parse an array value.
var array = [];
if (ch === '[') {
next('[');
white();
while (ch) {
if (ch === ']') {
next(']');
return array; // Potentially empty array
}
// ES5 allows omitting elements in arrays, e.g. [,] and
// [,null]. We don't allow this in JSON5.
if (ch === ',') {
error("Missing array element");
} else {
array.push(value());
}
white();
// If there's no comma after this value, this needs to
// be the end of the array.
if (ch !== ',') {
next(']');
return array;
}
next(',');
white();
}
}
error("Bad array");
},
object = function () {
// Parse an object value.
var key,
object = {};
if (ch === '{') {
next('{');
white();
while (ch) {
if (ch === '}') {
next('}');
return object; // Potentially empty object
}
// Keys can be unquoted. If they are, they need to be
// valid JS identifiers.
if (ch === '"' || ch === "'") {
key = string();
} else {
key = identifier();
}
white();
next(':');
object[key] = value();
white();
// If there's no comma after this pair, this needs to be
// the end of the object.
if (ch !== ',') {
next('}');
return object;
}
next(',');
white();
}
}
error("Bad object");
};
value = function () {
// Parse a JSON value. It could be an object, an array, a string, a number,
// or a word.
white();
switch (ch) {
case '{':
return object();
case '[':
return array();
case '"':
case "'":
return string();
case '-':
case '+':
case '.':
return number();
default:
return ch >= '0' && ch <= '9' ? number() : word();
}
};
// Return the json_parse function. It will have access to all of the above
// functions and variables.
return function (source, reviver) {
var result;
text = String(source);
at = 0;
lineNumber = 1;
columnNumber = 1;
ch = ' ';
result = value();
white();
if (ch) {
error("Syntax error");
}
// If there is a reviver function, we recursively walk the new structure,
// passing each name/value pair to the reviver function for possible
// transformation, starting with a temporary root object that holds the result
// in an empty key. If there is not a reviver function, we simply return the
// result.
return typeof reviver === 'function' ? (function walk(holder, key) {
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}({'': result}, '')) : result;
};
}());
// JSON5 stringify will not quote keys where appropriate
JSON5.stringify = function (obj, replacer, space) {
if (replacer && (typeof(replacer) !== "function" && !isArray(replacer))) {
throw new Error('Replacer must be a function or an array');
}
var getReplacedValueOrUndefined = function(holder, key, isTopLevel) {
var value = holder[key];
// Replace the value with its toJSON value first, if possible
if (value && value.toJSON && typeof value.toJSON === "function") {
value = value.toJSON();
}
// If the user-supplied replacer if a function, call it. If it's an array, check objects' string keys for
// presence in the array (removing the key/value pair from the resulting JSON if the key is missing).
if (typeof(replacer) === "function") {
return replacer.call(holder, key, value);
} else if(replacer) {
if (isTopLevel || isArray(holder) || replacer.indexOf(key) >= 0) {
return value;
} else {
return undefined;
}
} else {
return value;
}
};
function isWordChar(c) {
return (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c === '_' || c === '$';
}
function isWordStart(c) {
return (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
c === '_' || c === '$';
}
function isWord(key) {
if (typeof key !== 'string') {
return false;
}
if (!isWordStart(key[0])) {
return false;
}
var i = 1, length = key.length;
while (i < length) {
if (!isWordChar(key[i])) {
return false;
}
i++;
}
return true;
}
// export for use in tests
JSON5.isWord = isWord;
// polyfills
function isArray(obj) {
if (Array.isArray) {
return Array.isArray(obj);
} else {
return Object.prototype.toString.call(obj) === '[object Array]';
}
}
function isDate(obj) {
return Object.prototype.toString.call(obj) === '[object Date]';
}
var objStack = [];
function checkForCircular(obj) {
for (var i = 0; i < objStack.length; i++) {
if (objStack[i] === obj) {
throw new TypeError("Converting circular structure to JSON");
}
}
}
function makeIndent(str, num, noNewLine) {
if (!str) {
return "";
}
// indentation no more than 10 chars
if (str.length > 10) {
str = str.substring(0, 10);
}
var indent = noNewLine ? "" : "\n";
for (var i = 0; i < num; i++) {
indent += str;
}
return indent;
}
var indentStr;
if (space) {
if (typeof space === "string") {
indentStr = space;
} else if (typeof space === "number" && space >= 0) {
indentStr = makeIndent(" ", space, true);
} else {
// ignore space parameter
}
}
// Copied from Crokford's implementation of JSON
// See https://github.com/douglascrockford/JSON-js/blob/e39db4b7e6249f04a195e7dd0840e610cc9e941e/json2.js#L195
// Begin
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
};
function escapeString(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string' ?
c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' : '"' + string + '"';
}
// End
function internalStringify(holder, key, isTopLevel) {
var buffer, res;
// Replace the value, if necessary
var obj_part = getReplacedValueOrUndefined(holder, key, isTopLevel);
if (obj_part && !isDate(obj_part)) {
// unbox objects
// don't unbox dates, since will turn it into number
obj_part = obj_part.valueOf();
}
switch(typeof obj_part) {
case "boolean":
return obj_part.toString();
case "number":
if (isNaN(obj_part) || !isFinite(obj_part)) {
return "null";
}
return obj_part.toString();
case "string":
return escapeString(obj_part.toString());
case "object":
if (obj_part === null) {
return "null";
} else if (isArray(obj_part)) {
checkForCircular(obj_part);
buffer = "[";
objStack.push(obj_part);
for (var i = 0; i < obj_part.length; i++) {
res = internalStringify(obj_part, i, false);
buffer += makeIndent(indentStr, objStack.length);
if (res === null || typeof res === "undefined") {
buffer += "null";
} else {
buffer += res;
}
if (i < obj_part.length-1) {
buffer += ",";
} else if (indentStr) {
buffer += "\n";
}
}
objStack.pop();
if (obj_part.length) {
buffer += makeIndent(indentStr, objStack.length, true)
}
buffer += "]";
} else {
checkForCircular(obj_part);
buffer = "{";
var nonEmpty = false;
objStack.push(obj_part);
for (var prop in obj_part) {
if (obj_part.hasOwnProperty(prop)) {
var value = internalStringify(obj_part, prop, false);
isTopLevel = false;
if (typeof value !== "undefined" && value !== null) {
buffer += makeIndent(indentStr, objStack.length);
nonEmpty = true;
key = isWord(prop) ? prop : escapeString(prop);
buffer += key + ":" + (indentStr ? ' ' : '') + value + ",";
}
}
}
objStack.pop();
if (nonEmpty) {
buffer = buffer.substring(0, buffer.length-1) + makeIndent(indentStr, objStack.length) + "}";
} else {
buffer = '{}';
}
}
return buffer;
default:
// functions and undefined should be ignored
return undefined;
}
}
// special case...when undefined is used inside of
// a compound object/array, return null.
// but when top-level, return undefined
var topLevelHolder = {"":obj};
if (obj === undefined) {
return getReplacedValueOrUndefined(topLevelHolder, '', true);
}
return internalStringify(topLevelHolder, '', true);
};
@@ -0,0 +1,18 @@
// require.js
// Node.js only: adds a require() hook for .json5 files, just like the native
// hook for .json files.
//
// Usage:
// require('json5/require');
// require('./foo'); // will check foo.json5 after foo.js, foo.json, etc.
// require('./bar.json5');
var FS = require('fs');
var JSON5 = require('./json5');
// Modeled off of (v0.6.18 link; check latest too):
// https://github.com/joyent/node/blob/v0.6.18/lib/module.js#L468-L472
require.extensions['.json5'] = function (module, filename) {
var content = FS.readFileSync(filename, 'utf8');
module.exports = JSON5.parse(content);
};
+38
View File
@@ -0,0 +1,38 @@
{
"name": "json5",
"version": "0.5.1",
"description": "JSON for the ES5 era.",
"keywords": [
"json",
"es5"
],
"author": "Aseem Kishore <aseem.kishore@gmail.com>",
"contributors": [
"Max Nanasy <max.nanasy@gmail.com>",
"Andrew Eisenberg <andrew@eisenberg.as>",
"Jordan Tucker <jordanbtucker@gmail.com>"
],
"main": "lib/json5.js",
"bin": "lib/cli.js",
"files": [
"lib/"
],
"dependencies": {},
"devDependencies": {
"gulp": "^3.9.1",
"gulp-jshint": "^2.0.1",
"jshint": "^2.9.3",
"jshint-stylish": "^2.2.1",
"mocha": "^3.1.0"
},
"scripts": {
"build": "node ./lib/cli.js -c package.json5",
"test": "mocha --ui exports --reporter spec"
},
"homepage": "http://json5.org/",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/aseemk/json5.git"
}
}
@@ -0,0 +1,22 @@
(The MIT License)
Copyright (c) 2012 - 2015 Tobias Koppers
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,217 @@
# loader-utils
## Methods
### `getLoaderConfig`
Recommended way to retrieve the loader config:
```javascript
// inside your loader
config = loaderUtils.getLoaderConfig(this, "myLoader");
```
Tries to read the loader config from the `webpack.config.js` under the given property name (`"myLoader"` in this case) and merges the result with the loader query. For example, if your `webpack.config.js` had this property...
```javascript
cheesecakeLoader: {
type: "delicious",
slices: 4
}
```
...and your loader was called with `?slices=8`, `getLoaderConfig(this, "cheesecakeLoader")` would return
```javascript
{
type: "delicious",
slices: 8
}
```
It is recommended that you use the camelCased loader name as your default config property name.
### `parseQuery`
``` javascript
var query = loaderUtils.parseQuery(this.query);
assert(typeof query == "object");
if(query.flag)
// ...
```
``` text
null -> {}
? -> {}
?flag -> { flag: true }
?+flag -> { flag: true }
?-flag -> { flag: false }
?xyz=test -> { xyz: "test" }
?xyz[]=a -> { xyz: ["a"] }
?flag1&flag2 -> { flag1: true, flag2: true }
?+flag1,-flag2 -> { flag1: true, flag2: false }
?xyz[]=a,xyz[]=b -> { xyz: ["a", "b"] }
?a%2C%26b=c%2C%26d -> { "a,&b": "c,&d" }
?{json:5,data:{a:1}} -> { json: 5, data: { a: 1 } }
```
### `stringifyRequest`
Turns a request into a string that can be used inside `require()` or `import` while avoiding absolute paths.
Use it instead of `JSON.stringify(...)` if you're generating code inside a loader.
**Why is this necessary?** Since webpack calculates the hash before module paths are translated into module ids, we must avoid absolute paths to ensure
consistent hashes across different compilations.
This function:
- resolves absolute requests into relative requests if the request and the module are on the same hard drive
- replaces `\` with `/` if the request and the module are on the same hard drive
- won't change the path at all if the request and the module are on different hard drives
- applies `JSON.stringify` to the result
```javascript
loaderUtils.stringifyRequest(this, "./test.js");
// "\"./test.js\""
loaderUtils.stringifyRequest(this, ".\\test.js");
// "\"./test.js\""
loaderUtils.stringifyRequest(this, "test");
// "\"test\""
loaderUtils.stringifyRequest(this, "test/lib/index.js");
// "\"test/lib/index.js\""
loaderUtils.stringifyRequest(this, "otherLoader?andConfig!test?someConfig");
// "\"otherLoader?andConfig!test?someConfig\""
loaderUtils.stringifyRequest(this, require.resolve("test"));
// "\"../node_modules/some-loader/lib/test.js\""
loaderUtils.stringifyRequest(this, "C:\\module\\test.js");
// "\"../../test.js\"" (on Windows, in case the module and the request are on the same drive)
loaderUtils.stringifyRequest(this, "C:\\module\\test.js");
// "\"C:\\module\\test.js\"" (on Windows, in case the module and the request are on different drives)
loaderUtils.stringifyRequest(this, "\\\\network-drive\\test.js");
// "\"\\\\network-drive\\\\test.js\"" (on Windows, in case the module and the request are on different drives)
```
### `urlToRequest`
Converts some resource URL to a webpack module request.
```javascript
var url = "path/to/module.js";
var request = loaderUtils.urlToRequest(url); // "./path/to/module.js"
```
#### Module URLs
Any URL containing a `~` will be interpreted as a module request. Anything after the `~` will be considered the request path.
```javascript
var url = "~path/to/module.js";
var request = loaderUtils.urlToRequest(url); // "path/to/module.js"
```
#### Root-relative URLs
URLs that are root-relative (start with `/`) can be resolved relative to some arbitrary path by using the `root` parameter:
```javascript
var url = "/path/to/module.js";
var root = "./root";
var request = loaderUtils.urlToRequest(url, root); // "./root/path/to/module.js"
```
To convert a root-relative URL into a module URL, specify a `root` value that starts with `~`:
```javascript
var url = "/path/to/module.js";
var root = "~";
var request = loaderUtils.urlToRequest(url, root); // "path/to/module.js"
```
### `interpolateName`
Interpolates a filename template using multiple placeholders and/or a regular expression.
The template and regular expression are set as query params called `name` and `regExp` on the current loader's context.
```javascript
var interpolatedName = loaderUtils.interpolateName(loaderContext, name, options);
```
The following tokens are replaced in the `name` parameter:
* `[ext]` the extension of the resource
* `[name]` the basename of the resource
* `[path]` the path of the resource relative to the `context` query parameter or option.
* `[folder]` the folder of the resource is in.
* `[emoji]` a random emoji representation of `options.content`
* `[emoji:<length>]` same as above, but with a customizable number of emojis
* `[hash]` the hash of `options.content` (Buffer) (by default it's the hex digest of the md5 hash)
* `[<hashType>:hash:<digestType>:<length>]` optionally one can configure
* other `hashType`s, i. e. `sha1`, `md5`, `sha256`, `sha512`
* other `digestType`s, i. e. `hex`, `base26`, `base32`, `base36`, `base49`, `base52`, `base58`, `base62`, `base64`
* and `length` the length in chars
* `[N]` the N-th match obtained from matching the current file name against `options.regExp`
Examples
``` javascript
// loaderContext.resourcePath = "/app/js/javascript.js"
loaderUtils.interpolateName(loaderContext, "js/[hash].script.[ext]", { content: ... });
// => js/9473fdd0d880a43c21b7778d34872157.script.js
// loaderContext.resourcePath = "/app/page.html"
loaderUtils.interpolateName(loaderContext, "html-[hash:6].html", { content: ... });
// => html-9473fd.html
// loaderContext.resourcePath = "/app/flash.txt"
loaderUtils.interpolateName(loaderContext, "[hash]", { content: ... });
// => c31e9820c001c9c4a86bce33ce43b679
// loaderContext.resourcePath = "/app/img/image.gif"
loaderUtils.interpolateName(loaderContext, "[emoji]", { content: ... });
// => 👍
// loaderContext.resourcePath = "/app/img/image.gif"
loaderUtils.interpolateName(loaderContext, "[emoji:4]", { content: ... });
// => 🙍🏢📤🐝
// loaderContext.resourcePath = "/app/img/image.png"
loaderUtils.interpolateName(loaderContext, "[sha512:hash:base64:7].[ext]", { content: ... });
// => 2BKDTjl.png
// use sha512 hash instead of md5 and with only 7 chars of base64
// loaderContext.resourcePath = "/app/img/myself.png"
// loaderContext.query.name =
loaderUtils.interpolateName(loaderContext, "picture.png");
// => picture.png
// loaderContext.resourcePath = "/app/dir/file.png"
loaderUtils.interpolateName(loaderContext, "[path][name].[ext]?[hash]", { content: ... });
// => /app/dir/file.png?9473fdd0d880a43c21b7778d34872157
// loaderContext.resourcePath = "/app/js/page-home.js"
loaderUtils.interpolateName(loaderContext, "script-[1].[ext]", { regExp: "page-(.*)\\.js", content: ... });
// => script-home.js
```
### `getHashDigest`
``` javascript
var digestString = loaderUtils.getHashDigest(buffer, hashType, digestType, maxLength);
```
* `buffer` the content that should be hashed
* `hashType` one of `sha1`, `md5`, `sha256`, `sha512` or any other node.js supported hash type
* `digestType` one of `hex`, `base26`, `base32`, `base36`, `base49`, `base52`, `base58`, `base62`, `base64`
* `maxLength` the maximum length in chars
## License
MIT (http://www.opensource.org/licenses/mit-license.php)
@@ -0,0 +1,331 @@
var JSON5 = require("json5");
var path = require("path");
var util = require("util");
var os = require("os");
var assign = require("object-assign");
var emojiRegex = /[\uD800-\uDFFF]./;
var emojiList = require("emojis-list").filter(function(emoji) {
return emojiRegex.test(emoji)
});
var matchAbsolutePath = /^\/|^[A-Z]:[/\\]|^\\\\/i; // node 0.10 does not support path.isAbsolute()
var matchAbsoluteWin32Path = /^[A-Z]:[/\\]|^\\\\/i;
var matchRelativePath = /^\.\.?[/\\]/;
var baseEncodeTables = {
26: "abcdefghijklmnopqrstuvwxyz",
32: "123456789abcdefghjkmnpqrstuvwxyz", // no 0lio
36: "0123456789abcdefghijklmnopqrstuvwxyz",
49: "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ", // no lIO
52: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
58: "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ", // no 0lIO
62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
64: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"
};
var emojiCache = {};
var parseQueryDeprecationWarning = util.deprecate(function() {},
"loaderUtils.parseQuery() received a non-string value which can be problematic, " +
"see https://github.com/webpack/loader-utils/issues/56" + os.EOL +
"parseQuery() will be replaced with getOptions() in the next major version of loader-utils."
);
function encodeStringToEmoji(content, length) {
if (emojiCache[content]) return emojiCache[content];
length = length || 1;
var emojis = [];
do {
var index = Math.floor(Math.random() * emojiList.length);
emojis.push(emojiList[index]);
emojiList.splice(index, 1);
} while (--length > 0);
var emojiEncoding = emojis.join('');
emojiCache[content] = emojiEncoding;
return emojiEncoding;
}
function encodeBufferToBase(buffer, base) {
var encodeTable = baseEncodeTables[base];
if (!encodeTable) throw new Error("Unknown encoding base" + base);
var readLength = buffer.length;
var Big = require('big.js');
Big.RM = Big.DP = 0;
var b = new Big(0);
for (var i = readLength - 1; i >= 0; i--) {
b = b.times(256).plus(buffer[i]);
}
var output = "";
while (b.gt(0)) {
output = encodeTable[b.mod(base)] + output;
b = b.div(base);
}
Big.DP = 20;
Big.RM = 1;
return output;
}
exports.parseQuery = function parseQuery(query) {
var specialValues = {
'null': null,
'true': true,
'false': false
};
if(!query) return {};
if(typeof query !== "string") {
parseQueryDeprecationWarning();
return query;
}
if(query.substr(0, 1) !== "?")
throw new Error("a valid query string passed to parseQuery should begin with '?'");
query = query.substr(1);
var queryLength = query.length;
if(query.substr(0, 1) === "{" && query.substr(-1) === "}") {
return JSON5.parse(query);
}
var queryArgs = query.split(/[,\&]/g);
var result = {};
queryArgs.forEach(function(arg) {
var idx = arg.indexOf("=");
if(idx >= 0) {
var name = arg.substr(0, idx);
var value = decodeURIComponent(arg.substr(idx+1));
if (specialValues.hasOwnProperty(value)) {
value = specialValues[value];
}
if(name.substr(-2) === "[]") {
name = decodeURIComponent(name.substr(0, name.length-2));
if(!Array.isArray(result[name]))
result[name] = [];
result[name].push(value);
} else {
name = decodeURIComponent(name);
result[name] = value;
}
} else {
if(arg.substr(0, 1) === "-") {
result[decodeURIComponent(arg.substr(1))] = false;
} else if(arg.substr(0, 1) === "+") {
result[decodeURIComponent(arg.substr(1))] = true;
} else {
result[decodeURIComponent(arg)] = true;
}
}
});
return result;
};
exports.getLoaderConfig = function(loaderContext, defaultConfigKey) {
var query = exports.parseQuery(loaderContext.query);
var configKey = query.config || defaultConfigKey;
if (configKey) {
var config = loaderContext.options[configKey] || {};
delete query.config;
return assign({}, config, query);
}
return query;
};
exports.stringifyRequest = function(loaderContext, request) {
var splitted = request.split("!");
var context = loaderContext.context || (loaderContext.options && loaderContext.options.context);
return JSON.stringify(splitted.map(function(part) {
// First, separate singlePath from query, because the query might contain paths again
var splittedPart = part.match(/^(.*?)(\?.*)/);
var singlePath = splittedPart ? splittedPart[1] : part;
var query = splittedPart ? splittedPart[2] : "";
if(matchAbsolutePath.test(singlePath) && context) {
singlePath = path.relative(context, singlePath);
if(matchAbsolutePath.test(singlePath)) {
// If singlePath still matches an absolute path, singlePath was on a different drive than context.
// In this case, we leave the path platform-specific without replacing any separators.
// @see https://github.com/webpack/loader-utils/pull/14
return singlePath + query;
}
if(matchRelativePath.test(singlePath) === false) {
// Ensure that the relative path starts at least with ./ otherwise it would be a request into the modules directory (like node_modules).
singlePath = "./" + singlePath;
}
}
return singlePath.replace(/\\/g, "/") + query;
}).join("!"));
};
function dotRequest(obj) {
return obj.request;
}
exports.getRemainingRequest = function(loaderContext) {
if(loaderContext.remainingRequest)
return loaderContext.remainingRequest;
var request = loaderContext.loaders.slice(loaderContext.loaderIndex+1).map(dotRequest).concat([loaderContext.resource]);
return request.join("!");
};
exports.getCurrentRequest = function(loaderContext) {
if(loaderContext.currentRequest)
return loaderContext.currentRequest;
var request = loaderContext.loaders.slice(loaderContext.loaderIndex).map(dotRequest).concat([loaderContext.resource]);
return request.join("!");
};
exports.isUrlRequest = function(url, root) {
// An URL is not an request if
// 1. it's a Data Url
// 2. it's an absolute url or and protocol-relative
// 3. it's some kind of url for a template
if(/^data:|^chrome-extension:|^(https?:)?\/\/|^[\{\}\[\]#*;,'§\$%&\(=?`´\^°<>]/.test(url)) return false;
// 4. It's also not an request if root isn't set and it's a root-relative url
if((root === undefined || root === false) && /^\//.test(url)) return false;
return true;
};
exports.urlToRequest = function(url, root) {
var moduleRequestRegex = /^[^?]*~/;
var request;
if(matchAbsoluteWin32Path.test(url)) {
// absolute windows path, keep it
request = url;
} else if(root !== undefined && root !== false && /^\//.test(url)) {
// if root is set and the url is root-relative
switch(typeof root) {
// 1. root is a string: root is prefixed to the url
case "string":
// special case: `~` roots convert to module request
if (moduleRequestRegex.test(root)) {
request = root.replace(/([^~\/])$/, "$1/") + url.slice(1);
} else {
request = root + url;
}
break;
// 2. root is `true`: absolute paths are allowed
// *nix only, windows-style absolute paths are always allowed as they doesn't start with a `/`
case "boolean":
request = url;
break;
default:
throw new Error("Unexpected parameters to loader-utils 'urlToRequest': url = " + url + ", root = " + root + ".");
}
} else if(/^\.\.?\//.test(url)) {
// A relative url stays
request = url;
} else {
// every other url is threaded like a relative url
request = "./" + url;
}
// A `~` makes the url an module
if (moduleRequestRegex.test(request)) {
request = request.replace(moduleRequestRegex, "");
}
return request;
};
exports.parseString = function parseString(str) {
try {
if(str[0] === '"') return JSON.parse(str);
if(str[0] === "'" && str.substr(str.length - 1) === "'") {
return parseString(str.replace(/\\.|"/g, function(x) {
if(x === '"') return '\\"';
return x;
}).replace(/^'|'$/g, '"'));
}
return JSON.parse('"' + str + '"');
} catch(e) {
return str;
}
};
exports.getHashDigest = function getHashDigest(buffer, hashType, digestType, maxLength) {
hashType = hashType || "md5";
maxLength = maxLength || 9999;
var hash = require("crypto").createHash(hashType);
hash.update(buffer);
if (digestType === "base26" || digestType === "base32" || digestType === "base36" ||
digestType === "base49" || digestType === "base52" || digestType === "base58" ||
digestType === "base62" || digestType === "base64") {
return encodeBufferToBase(hash.digest(), digestType.substr(4)).substr(0, maxLength);
} else {
return hash.digest(digestType || "hex").substr(0, maxLength);
}
};
exports.interpolateName = function interpolateName(loaderContext, name, options) {
var filename;
if (typeof name === "function") {
filename = name(loaderContext.resourcePath);
} else {
filename = name || "[hash].[ext]";
}
var context = options.context;
var content = options.content;
var regExp = options.regExp;
var ext = "bin";
var basename = "file";
var directory = "";
var folder = "";
if(loaderContext.resourcePath) {
var resourcePath = loaderContext.resourcePath;
var idx = resourcePath.lastIndexOf(".");
var i = resourcePath.lastIndexOf("\\");
var j = resourcePath.lastIndexOf("/");
var p = i < 0 ? j : j < 0 ? i : i < j ? i : j;
if(idx >= 0) {
ext = resourcePath.substr(idx+1);
resourcePath = resourcePath.substr(0, idx);
}
if(p >= 0) {
basename = resourcePath.substr(p+1);
resourcePath = resourcePath.substr(0, p+1);
}
if (typeof context !== 'undefined') {
directory = path.relative(context, resourcePath + "_").replace(/\\/g, "/").replace(/\.\.(\/)?/g, "_$1");
directory = directory.substr(0, directory.length-1);
}
else {
directory = resourcePath.replace(/\\/g, "/").replace(/\.\.(\/)?/g, "_$1");
}
if (directory.length === 1) {
directory = "";
} else if (directory.length > 1) {
folder = path.basename(directory);
}
}
var url = filename;
if(content) {
// Match hash template
url = url.replace(/\[(?:(\w+):)?hash(?::([a-z]+\d*))?(?::(\d+))?\]/ig, function() {
return exports.getHashDigest(content, arguments[1], arguments[2], parseInt(arguments[3], 10));
}).replace(/\[emoji(?::(\d+))?\]/ig, function() {
return encodeStringToEmoji(content, arguments[1]);
});
}
url = url.replace(/\[ext\]/ig, function() {
return ext;
}).replace(/\[name\]/ig, function() {
return basename;
}).replace(/\[path\]/ig, function() {
return directory;
}).replace(/\[folder\]/ig, function() {
return folder;
});
if(regExp && loaderContext.resourcePath) {
var re = new RegExp(regExp);
var match = loaderContext.resourcePath.match(re);
if(match) {
for (var i = 0; i < match.length; i++) {
var re = new RegExp("\\[" + i + "\\]", "ig");
url = url.replace(re, match[i]);
}
}
}
if(typeof loaderContext.options === "object" && typeof loaderContext.options.customInterpolateName === "function") {
url = loaderContext.options.customInterpolateName.call(loaderContext, url, name, options);
}
return url;
};
@@ -0,0 +1,31 @@
{
"name": "loader-utils",
"version": "0.2.17",
"author": "Tobias Koppers @sokra",
"description": "utils for webpack loaders",
"dependencies": {
"big.js": "^3.1.3",
"emojis-list": "^2.0.0",
"json5": "^0.5.0",
"object-assign": "^4.0.1"
},
"scripts": {
"test": "mocha",
"travis": "npm run cover -- --report lcovonly",
"cover": "istanbul cover -x *.runtime.js node_modules/mocha/bin/_mocha",
"publish-patch": "mocha && npm version patch && git push && git push --tags && npm publish"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/webpack/loader-utils.git"
},
"devDependencies": {
"coveralls": "^2.11.2",
"istanbul": "^0.3.14",
"mocha": "^1.21.4"
},
"files": [
"index.js"
]
}
+118
View File
@@ -0,0 +1,118 @@
{
"name": "@vue/cli-service",
"version": "4.4.6",
"description": "local service for vue-cli projects",
"main": "lib/Service.js",
"typings": "types/index.d.ts",
"bin": {
"vue-cli-service": "bin/vue-cli-service.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue-cli.git",
"directory": "packages/@vue/cli-service"
},
"keywords": [
"vue",
"cli"
],
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/vue-cli/issues"
},
"homepage": "https://cli.vuejs.org/",
"dependencies": {
"@intervolga/optimize-cssnano-plugin": "^1.0.5",
"@soda/friendly-errors-webpack-plugin": "^1.7.1",
"@soda/get-current-script": "^1.0.0",
"@vue/cli-overlay": "^4.4.6",
"@vue/cli-plugin-router": "^4.4.6",
"@vue/cli-plugin-vuex": "^4.4.6",
"@vue/cli-shared-utils": "^4.4.6",
"@vue/component-compiler-utils": "^3.1.2",
"@vue/preload-webpack-plugin": "^1.1.0",
"@vue/web-component-wrapper": "^1.2.0",
"acorn": "^7.2.0",
"acorn-walk": "^7.1.1",
"address": "^1.1.2",
"autoprefixer": "^9.8.0",
"browserslist": "^4.12.0",
"cache-loader": "^4.1.0",
"case-sensitive-paths-webpack-plugin": "^2.3.0",
"cli-highlight": "^2.1.4",
"clipboardy": "^2.3.0",
"cliui": "^6.0.0",
"copy-webpack-plugin": "^5.1.1",
"css-loader": "^3.5.3",
"cssnano": "^4.1.10",
"debug": "^4.1.1",
"default-gateway": "^5.0.5",
"dotenv": "^8.2.0",
"dotenv-expand": "^5.1.0",
"file-loader": "^4.2.0",
"fs-extra": "^7.0.1",
"globby": "^9.2.0",
"hash-sum": "^2.0.0",
"html-webpack-plugin": "^3.2.0",
"launch-editor-middleware": "^2.2.1",
"lodash.defaultsdeep": "^4.6.1",
"lodash.mapvalues": "^4.6.0",
"lodash.transform": "^4.6.0",
"mini-css-extract-plugin": "^0.9.0",
"minimist": "^1.2.5",
"pnp-webpack-plugin": "^1.6.4",
"portfinder": "^1.0.26",
"postcss-loader": "^3.0.0",
"ssri": "^7.1.0",
"terser-webpack-plugin": "^2.3.6",
"thread-loader": "^2.1.3",
"url-loader": "^2.2.0",
"vue-loader": "^15.9.2",
"vue-style-loader": "^4.1.2",
"webpack": "^4.0.0",
"webpack-bundle-analyzer": "^3.8.0",
"webpack-chain": "^6.4.0",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^4.2.2"
},
"peerDependencies": {
"vue-template-compiler": "^2.0.0"
},
"peerDependenciesMeta": {
"less-loader": {
"optional": true
},
"pug-plain-loader": {
"optional": true
},
"raw-loader": {
"optional": true
},
"sass-loader": {
"optional": true
},
"stylus-loader": {
"optional": true
},
"vue-template-compiler": {
"optional": true
}
},
"devDependencies": {
"fibers": ">= 3.1.1 <5.0.0",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"vue": "^2.6.11",
"vue-router": "^3.2.0",
"vue-template-compiler": "^2.6.11",
"vuex": "^3.4.0"
},
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=8"
},
"gitHead": "e311b062cb388f4d6230ea45cdc3bd3512b88b61"
}
+60
View File
@@ -0,0 +1,60 @@
import ChainableWebpackConfig from 'webpack-chain'
import { WebpackOptions } from 'webpack/declarations/WebpackOptions'
type PageEntry = string | string[];
interface PageConfig {
entry: PageEntry;
[key: string]: any;
}
interface LoaderOptions {
css?: object;
sass?: object;
less?: object;
stylus?: object;
postcss?: object;
}
// mini-css-extract-plugin options
interface ExtractOptions {
filename?: string;
chunkFilename?: string;
}
interface CSSOptions {
requireModuleExtension?: boolean;
extract?: boolean | ExtractOptions;
sourceMap?: boolean;
loaderOptions?: LoaderOptions;
}
export interface ProjectOptions {
publicPath?: string;
outputDir?: string;
assetsDir?: string;
indexPath?: string;
filenameHashing?: boolean;
runtimeCompiler?: boolean;
transpileDependencies?: Array<string | RegExp>;
productionSourceMap?: boolean;
parallel?: boolean | number;
devServer?: object;
pages?: {
[key: string]: PageEntry | PageConfig;
};
crossorigin?: '' | 'anonymous' | 'use-credentials';
integrity?: boolean;
css?: CSSOptions;
chainWebpack?: (config: ChainableWebpackConfig) => void;
configureWebpack?: WebpackOptions | ((config: WebpackOptions) => (WebpackOptions | void));
lintOnSave?: boolean | 'default' | 'warning' | 'error';
pwa?: object;
pluginOptions?: object;
}
export type ConfigFunction = () => ProjectOptions
+1
View File
@@ -0,0 +1 @@
export { ProjectOptions, ConfigFunction } from './ProjectOptions'
+12
View File
@@ -0,0 +1,12 @@
// this file is for cases where we need to access the
// webpack config as a file when using CLI commands.
let service = process.VUE_CLI_SERVICE
if (!service || process.env.VUE_CLI_API_MODE) {
const Service = require('./lib/Service')
service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd())
service.init(process.env.VUE_CLI_MODE || process.env.NODE_ENV)
}
module.exports = service.resolveWebpackConfig()