@xyezir/vux-loader
Version:
extended loader for vux
612 lines (528 loc) • 17.3 kB
JavaScript
const path = require('path')
const fs = require('fs')
const merge = require('webpack-merge')
const utils = require('loader-utils')
const less = require('less')
const yaml = require('js-yaml')
const _ = require('lodash')
const pkg = require('../package')
var webpack = require('webpack')
const scriptLoader = path.join(__dirname, './script-loader.js')
const noopLoader = path.join(__dirname, './noop-loader.js')
const styleLoader = path.join(__dirname, './style-loader.js')
const templateLoader = path.join(__dirname, './template-loader.js')
const jsLoader = path.join(__dirname, './js-loader.js')
const afterLessLoader = path.join(__dirname, './after-less-loader.js')
const beforeTemplateCompilerLoader = path.join(__dirname, './before-template-compiler-loader.js')
const projectRoot = process.cwd()
const getLessVariables = require('./libs/get-less-variables')
/** build done callback **/
function DonePlugin(callbacks) {
this.callbacks = callbacks || function () {}
// Setup the plugin instance with options...
}
DonePlugin.prototype.apply = function (compiler) {
let callbacks = this.callbacks
compiler.plugin('done', function () {
callbacks.forEach(function (fn) {
fn()
})
});
};
/** emit plugin **/
function EmitPlugin(callback) {
this.callback = callback
}
EmitPlugin.prototype.apply = function (compiler) {
let callback = this.callback
compiler.plugin("emit", function (compilation, cb) {
callback(compilation, cb)
});
}
function hasPlugin(name, list) {
const match = list.filter(function (one) {
return one.name === name
})
return match.length > 0
}
function getFirstPlugin(name, list) {
const match = list.filter(function (one) {
return one.name === name
})
return match[0]
}
module.exports.merge = function (oldConfig, vuxConfig) {
oldConfig.module.rules.push({
resourceQuery: /^\?vue&type=template/,
enforce: 'pre',
loader: path.resolve(__dirname, './template-loader')
})
oldConfig.module.rules.push({
resourceQuery: /\.js/,
loader: path.resolve(__dirname, './js-loader')
})
oldConfig.module.rules.push({
resourceQuery: /^\?vue&type=script/,
enforce: 'pre',
loader: path.resolve(__dirname, './script-loader')
})
oldConfig.module.rules.push({
resourceQuery: /blockType=i18n/,
loader: path.resolve(__dirname, './i18n-loader')
})
let variables = {}
oldConfig = Object.assign({
plugins: []
}, oldConfig)
let config = Object.assign({
module: {},
plugins: []
}, oldConfig)
if (!vuxConfig) {
vuxConfig = {
options: {},
plugins: []
}
}
if (!vuxConfig.options) {
vuxConfig.options = {
buildEnvs: ['production']
}
}
if (typeof vuxConfig.options.ssr === 'undefined') {
vuxConfig.options.ssr = false
}
const buildEnvs = vuxConfig.options.buildEnvs || ['production']
if (buildEnvs.indexOf(process.env.NODE_ENV) !== -1) {
process.env.__VUX_BUILD__ = true
} else {
process.env.__VUX_BUILD__ = false
}
if (!vuxConfig.plugins) {
vuxConfig.plugins = []
}
if (vuxConfig.plugins.length) {
vuxConfig.plugins = vuxConfig.plugins.map(function (plugin) {
if (typeof plugin === 'string') {
return {
name: plugin
}
}
return plugin
})
}
vuxConfig.allPlugins = vuxConfig.allPlugins || []
// check multi plugin instance
const pluginGroup = _.groupBy(vuxConfig.plugins, function (plugin) {
return plugin.name
})
for (let group in pluginGroup) {
if (pluginGroup[group].length > 1) {
throw (`only one instance is allowed. plugin name: ${group}`)
}
}
// if exists old vux config, merge options and plugins list
let oldVuxConfig = oldConfig.vux || null
oldConfig.plugins.forEach(function (plugin) {
if (plugin.constructor.name === 'LoaderOptionsPlugin' && plugin.options.vux) {
oldVuxConfig = plugin.options.vux
}
})
if (oldVuxConfig) {
// merge old options
vuxConfig.options = Object.assign(oldVuxConfig.options, vuxConfig.options)
// merge old plugins list
vuxConfig.plugins.forEach(function (newPlugin) {
let isSame = false
oldVuxConfig.allPlugins.forEach(function (oldPlugin, index) {
if (newPlugin.name === oldPlugin.name) {
oldVuxConfig.allPlugins.splice(index, 1)
oldVuxConfig.allPlugins.push(newPlugin)
isSame = true
}
})
if (!isSame) {
oldVuxConfig.allPlugins.push(newPlugin)
}
})
vuxConfig.allPlugins = oldVuxConfig.allPlugins
} else {
vuxConfig.allPlugins = vuxConfig.plugins
}
// filter plugins by env
if (vuxConfig.options.env && vuxConfig.allPlugins.length) {
vuxConfig.plugins = vuxConfig.allPlugins.filter(function (plugin) {
return typeof plugin.envs === 'undefined' || (typeof plugin.envs === 'object' && plugin.envs.length && plugin.envs.indexOf(vuxConfig.options.env) > -1)
})
}
if (!vuxConfig.options.projectRoot) {
vuxConfig.options.projectRoot = projectRoot
}
var themes = vuxConfig.plugins.filter(function (plugin) {
return plugin.name === 'less-theme'
})
if (themes.length) {
const themePath = path.join(vuxConfig.options.projectRoot, themes[0].path)
variables = getLessVariables(themes[0].path)
}
let vuxVersion
try {
let vuePackagePath = path.resolve(vuxConfig.options.projectRoot, 'node_modules/vux/package.json')
vuxVersion = require(vuePackagePath).version
} catch (e) {}
// get vue version
let vueVersion
try {
let vuePackagePath = path.resolve(vuxConfig.options.projectRoot, 'node_modules/vue/package.json')
vueVersion = require(vuePackagePath).version
} catch (e) {}
vuxConfig.options.vueVersion = vueVersion
let vueLoaderVersion
try {
let vueLoaderPackagePath = path.resolve(vuxConfig.options.projectRoot, 'node_modules/vue-loader/package.json')
vueLoaderVersion = require(vueLoaderPackagePath).version
} catch (e) {}
vuxConfig.options.vueLoaderVersion = vueLoaderVersion
require('./libs/report')({
vueVersion: vueVersion,
vuxVersion: vuxVersion,
vueLoaderVersion: vueLoaderVersion
})
// check webpack version by module.loaders
let isWebpack2
if (typeof vuxConfig.options.isWebpack2 !== 'undefined') {
isWebpack2 = vuxConfig.options.isWebpack2
} else if (oldConfig.module && oldConfig.module.rules) {
isWebpack2 = true
} else if (oldConfig.module && oldConfig.module.loaders) {
isWebpack2 = false
}
if (typeof isWebpack2 === 'undefined') {
const compareVersions = require('compare-versions')
const pkg = require(path.resolve(vuxConfig.options.projectRoot, 'package.json'))
if (pkg.devDependencies.webpack) {
isWebpack2 = compareVersions(pkg.devDependencies.webpack.replace('^', '').replace('~', ''), '2.0.0') > -1
} else {
isWebpack2 = true
}
}
if (!isWebpack2) {
if (!config.vue) {
config.vue = {
loaders: {
i18n: '@xyezir/vux-loader/src/noop-loader.js'
}
}
} else {
if (!config.vue.loaders) {
config.vue.loaders = {}
}
config.vue.loaders.i18n = '@xyezir/vux-loader/src/noop-loader.js'
}
}
let loaderKey = 'rules' // isWebpack2 ? 'rules' : 'loaders'
config.module[loaderKey] = config.module[loaderKey] || []
const useVuxUI = hasPlugin('vux-ui', vuxConfig.plugins)
vuxConfig.options.useVuxUI = true
/**
* ======== set vux options ========
*/
// for webpack@2.x, options should be provided with LoaderOptionsPlugin
if (isWebpack2) {
if (!config.plugins) {
config.plugins = []
}
// delete old config for webpack2
config.plugins.forEach(function (plugin, index) {
if (plugin.constructor.name === 'LoaderOptionsPlugin' && plugin.options.vux) {
config.plugins.splice(index, 1)
}
})
config.plugins.push(new webpack.LoaderOptionsPlugin({
vux: vuxConfig
}))
} else { // for webpack@1.x, merge directly
config = merge(config, {
vux: vuxConfig
})
}
// if (hasPlugin('inline-manifest', vuxConfig.plugins)) {
// var InlineManifestWebpackPlugin = require('inline-manifest-webpack-plugin')
// config.plugins.push(new InlineManifestWebpackPlugin({
// name: 'webpackManifest'
// }))
// }
if (hasPlugin('progress-bar', vuxConfig.plugins)) {
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
const pluginConfig = getFirstPlugin('progress-bar', vuxConfig.plugins)
config.plugins.push(new ProgressBarPlugin(pluginConfig.options || {}))
}
if (hasPlugin('vux-ui', vuxConfig.plugins)) {
let mapPath = path.resolve(vuxConfig.options.projectRoot, 'node_modules/vux/src/components/map.json')
if (vuxConfig.options.vuxDev) {
mapPath = path.resolve(vuxConfig.options.projectRoot, 'src/components/map.json')
}
const maps = require(mapPath)
if (isWebpack2) {
config.plugins.push(new webpack.LoaderOptionsPlugin({
vuxMaps: maps
}))
} else {
config = merge(config, {
vuxMaps: maps
})
}
}
// get less variable alias
if (hasPlugin('vux-ui', vuxConfig.plugins)) {
let variablePath = path.resolve(vuxConfig.options.projectRoot, 'node_modules/vux/src/styles/variable.less')
if (vuxConfig.options.vuxDev) {
variablePath = path.resolve(vuxConfig.options.projectRoot, 'src/styles/variable.less')
}
// parse alias
const rs = {}
try {
const content = fs.readFileSync(variablePath, 'utf-8').split('\n').filter(line => /\/\/\salias/.test(line)).map(line => {
const value = line.split('// alias ')[1].replace(/\s+/g, '').trim()
const key = line.split('// alias ')[0].replace(/\s+/g, '').trim().split(':')[0].replace(/^@/, '')
return [key, value]
}).forEach(one => {
rs[one[0]] = one[1]
})
} catch (e) {}
if (isWebpack2) {
config.plugins.push(new webpack.LoaderOptionsPlugin({
vuxVariableMap: rs
}))
} else {
config = merge(config, {
vuxVariableMap: rs
})
}
}
/**
* ======== read vux locales and set globally ========
*/
if (hasPlugin('vux-ui', vuxConfig.plugins)) {
let vuxLocalesPath = path.resolve(vuxConfig.options.projectRoot, 'node_modules/vux/src/locales/all.yml')
if (vuxConfig.options.vuxDev) {
vuxLocalesPath = path.resolve(vuxConfig.options.projectRoot, 'src/locales/all.yml')
}
try {
const vuxLocalesContent = fs.readFileSync(vuxLocalesPath, 'utf-8')
let vuxLocalesJson = yaml.safeLoad(vuxLocalesContent)
if (isWebpack2) {
config.plugins.push(new webpack.LoaderOptionsPlugin({
vuxLocales: vuxLocalesJson
}))
} else {
config = merge(config, {
vuxLocales: vuxLocalesJson
})
}
} catch (e) {}
}
/**
* ======== append vux-loader ========
*/
let loaderString = vuxConfig.options.loaderString || '@xyezir/vux-loader!vue-loader'
const rewriteConfig = vuxConfig.options.rewriteLoaderString
if (typeof rewriteConfig === 'undefined' || rewriteConfig === true) {
let hasAppendVuxLoader = false
config.module.rules.forEach(function (rule) {
const hasVueLoader = typeof rule.use === 'object' && rule.use.filter(one => {
return one.loader === 'vue-loader'
}).length > 0
})
}
/**
* ======== append js-loader for ts-loader ========
*/
config.module[loaderKey].forEach(function (rule) {
if (rule.use && (rule.use[0] === 'ts-loader' || (typeof rule.use[0] === 'object' && rule.use[0].loader === 'ts-loader'))) {
rule.use.push(jsLoader)
} else {
if (rule.loader === 'ts' || rule.loader === 'ts-loader' || (/\bts\b/.test(rule.loader) && !/!/.test(rule.loader))) {
if (isWebpack2 && (rule.query || rule.options)) {
let options
if(rule.options){
options = rule.options
delete rule.options
}else{
options = rule.query
delete rule.query
}
rule.use = [{
loader: 'ts-loader',
options: options
}, jsLoader]
delete rule.loader
} else {
rule.loader = 'ts-loader!' + jsLoader
}
}
}
})
/**
* ======== append js-loader ========
*/
config.module[loaderKey].forEach(function (rule) {
if (Array.isArray(rule.use)) {
if (rule.use.filter(one => one.loader === 'babel-loader').length) {
rule.use.push({
loader: jsLoader
})
}
} else {
if (rule.loader === 'babel' || rule.loader === 'babel-loader' || (/babel/.test(rule.loader) && !/!/.test(rule.loader))) {
if (isWebpack2 && (rule.query || rule.options)) {
let options
if(rule.options){
options = rule.options
delete rule.options
}else{
options = rule.query
delete rule.query
}
rule.use = [{
loader: 'babel-loader',
options: options
}, jsLoader]
delete rule.loader
} else {
rule.loader = 'babel-loader!' + jsLoader
}
}
}
})
config.module.rules.forEach(function (rule) {
if (Array.isArray(rule.oneOf)) {
rule.oneOf.forEach(function (one) {
one.use.forEach(function (line) {
if (line.loader === 'less-loader') {
line.options = Object.assign(line.options || {}, {
modifyVars: variables
})
}
})
})
}
})
/**
* ======== set compiling vux js source ========
*/
if (hasPlugin('vux-ui', vuxConfig.plugins)) {
// if (typeof vuxConfig.options.vuxSetBabel === 'undefined' || vuxConfig.options.vuxSetBabel === true) {
// config.module[loaderKey].push(getBabelLoader(vuxConfig.options.projectRoot, 'vux', vuxConfig.options.vuxDev))
// }
}
// set done plugin
if (hasPlugin('build-done-callback', vuxConfig.plugins)) {
const callbacks = vuxConfig.plugins.filter(function (one) {
return one.name === 'build-done-callback'
}).map(function (one) {
return one.fn
})
config.plugins.push(new DonePlugin(callbacks))
}
config.plugins.push(new DonePlugin([function () {
if (global.reportInterval) {
clearInterval(global.reportInterval)
global.reportInterval = null
}
}]))
if (hasPlugin('build-emit-callback', vuxConfig.plugins)) {
config.plugins = config.plugins || []
const callbacks = vuxConfig.plugins.filter(function (one) {
return one.name === 'build-emit-callback'
}).map(function (one) {
return one.fn
})
if (callbacks.length) {
config.plugins.push(new EmitPlugin(callbacks[0]))
}
}
/**
*======== global variable V_LOCALE ========
*/
let locale = ''
if (hasPlugin('i18n', vuxConfig.plugins)) {
const config = getFirstPlugin('i18n', vuxConfig.plugins)
if (config.vuxStaticReplace && config.vuxLocale) {
locale = config.vuxLocale
} else if (config.vuxStaticReplace === false) {
locale = 'MULTI'
}
} else {
locale = 'zh-CN'
}
/**
*======== global variable V_SSR ========
*/
let ssr = false
if (vuxConfig.options.ssr) {
ssr = true
}
// check if already defined V_LOCALE
let matchLocale = config.plugins.filter(one => {
if (one.constructor.name === 'DefinePlugin') {
if (one.definitions && one.definitions.V_LOCALE) {
return true
}
}
return false
})
if (!matchLocale.length) {
config.plugins.push(new webpack.DefinePlugin({
V_LOCALE: JSON.stringify(locale),
V_SSR: JSON.stringify(ssr),
SUPPORT_SSR_TAG: JSON.stringify(true)
}))
}
// save to file so it can be read when using thread-loader
const cacheFile = require.resolve('vux').replace('index.js', '.config.cache.json')
fs.writeFileSync(cacheFile, JSON.stringify(vuxConfig, null, 2))
oldConfig = merge(oldConfig, config)
return config
}
/**
* use babel so component's js can be compiled
*/
function getBabelLoader(projectRoot, name, isDev) {
name = name || 'vux'
if (!projectRoot) {
projectRoot = path.resolve(__dirname, '../../../')
if (/\.npm/.test(projectRoot)) {
projectRoot = path.resolve(projectRoot, '../../../')
}
}
let componentPath
let regex
if (!isDev) {
componentPath = fs.realpathSync(projectRoot + `/node_modules/${name}/`) // https://github.com/webpack/webpack/issues/1643
regex = new RegExp(`node_modules.*${name}.src.*?js$`)
} else {
componentPath = projectRoot
regex = new RegExp(`${projectRoot}.src.*?js$`)
}
return {
test: regex,
loader: 'babel-loader',
include: componentPath
}
}
function setWebpackConfig(oriConfig, appendConfig, isWebpack2) {
if (isWebpack2) {
oriConfig.plugins.push(new webpack.LoaderOptionsPlugin(appendConfig))
} else {
oriConfig = merge(oriConfig, appendConfig)
}
return oriConfig
}
function getOnePlugin(name, plugins) {
const matches = plugins.filter(function (one) {
return one.name === name
})
return matches.length ? matches[0] : null
}