Open saxon-yc opened 5 years ago
@zhixueyun9 如果是显示没有按需加载组件,试下这样写
// main.js // import { LoadingPlugin } from "vux"; // notice: 不要使用解构语法,否则打包会全部打包进去(BUG?) import ToastPlugin from "vux/src/plugins/toast"; Vue.use(ToastPlugin, { position: "bottom", type: "text", width: "14.6em" });
您好,为什么只有在main.js里不能用解构呢
@iamziyue 把vux在main.js全局引用就行,不要单独在vue组件里面引用。
我在用@vux/loader 2.0.0-rc4替换vux-loader 1.2.9之后,发现的确是这样的,如果在vue文件引用,就必须用import XHeader from "vux/src/components/x-header"。
不过再替换之前,在main.js不能用import { XHeader } from 'vux',在vue文件里面可以,替换之后就反过来。。。
不能在vue文件使用解构,改起来工作量有点大。
// 我使用这行代码会出现(如果你看到这一行,说明 vux-loader 配置有问题或者代码书写规范的原因导致无法解析成按需引入组件,会导致打包体积过大。请升级到最新版本 vux-loader,建议开启 eslint(standard)。)的提示
import { Group, Cell } from 'vux';
// 使用本行代码后警告消失
import Group from "vux/src/components/group";
import Cell from "vux/src/components/cell";
我的package 如下
"dependencies": {
"axios": "^0.19.0",
"mockjs": "^1.1.0",
"vue": "^2.6.10",
"vue-i18n": "^8.15.0",
"vue-router": "^3.1.3",
"vux": "^2.9.4"
},
"devDependencies": {
"@babel/core": "^7.6.4",
"@babel/register": "^7.6.2",
"@commitlint/cli": "^8.2.0",
"@commitlint/config-conventional": "^8.2.0",
"@types/node": "^12.12.3",
"@vue/cli-plugin-babel": "^4.0.0",
"@vue/cli-plugin-eslint": "^4.0.0",
"@vue/cli-service": "^4.0.0",
"@vux/loader": "^2.0.0-rc4",
"babel-core": "^6.26.3",
"babel-eslint": "^10.0.3",
"chalk": "^2.4.2",
"chokidar": "^3.2.3",
"commitizen": "^4.0.3",
"core-js": "^3.3.6",
"cz-conventional-changelog": "^3.0.2",
"cz-customizable": "^6.2.0",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"gitmoji-cli": "^2.2.0",
"husky": "^3.0.9",
"less-loader": "^5.0.0",
"node-sass": "^4.13.0",
"sass-loader": "^8.0.0",
"script-ext-html-webpack-plugin": "^2.1.4",
"svg-sprite-loader": "^4.1.6",
"vue-loader": "^15.7.1",
"vue-template-compiler": "^2.6.10"
},
有没有其他的方法统一配置一下,可以支持 import { Group, Cell } from 'vux'; 这种方式引入组件
看了一下其他项目的类似实现,element-ui 使用的是 babel-plugin-components, ant-mobile 以及国内其他几个库使用的也是自己的插件 babel-plugin-import,看了下源码,倒也大概能支持 vux 的按需加载需要。
说一下我的方法哈:
yarn add -D babel-plugin-import
;babel.config.js
如下:
const maps = require('./node_modules/vux/src/components/map.json')
module.exports = { presets: [ '@vue/cli-plugin-babel/preset' ], plugins: [ ['import', { libraryName: 'vux', libraryDirectory: 'src/components', style: false, camel2UnderlineComponentName: false, camel2DashComponentName: false, customName: (transformedMethodName) => { return 'vux/' + maps[transformedMethodName] } }] ] }
然后就可以愉快的使用类似 `import { XButton } from 'vux'` 这样的语句按需引入了。
当然目测这个插件仅支持处理 js 组件的 import,不过,倒应该也能解决 80% 以上同学的需求了,而且,我本地 cli 4 没什么问题,cli 3 应该也不会有什么意外。
reset.less 可以在 app.vue 的 style 块用 less 语法引入,也可以直接在 main.js 中通过 import 语句引入,如 `import 'vux/src/styles/reset.less'`;
### theme.less
编辑 `vue.config.js` (如果没有的话,在项目根目录新建一个),大概如下 less 块(@vux/loader 还是需要的 :) ):
``` javascript
const getLessVariables = require('./node_modules/@vux/loader/src/libs/get-less-variables')
module.exports = {
css: {
loaderOptions: {
// sass: {
// prependData: `@import "@/assets/style/_setting.scss";`
// },
less: {
modifyVars: getLessVariables('./src/assets/style/theme.less')
},
}
},
}
如果不用的话,暂时在 main.js 文件添加代码,如下:
const VuxLocals = {
'cancel': '取消',
'button_text': '确定',
'confirm_text': '确定',
'cancel_text': '取消',
'week_day_0': '日',
'week_day_1': '一',
'week_day_2': '二',
'week_day_3': '三',
'week_day_4': '四',
'week_day_5': '五',
'week_day_6': '六',
'loading': '加载中',
'search.placeholder': '搜索',
'back_text': '返回',
}
Vue.prototype.$t = t => VuxLocals[t] || t
如有从 webpack 的 template-loader 层面处理的办法,麻烦分享补充哈。
其他如 i18n,yml 那一块的 loader 这个就无能为力了,还要多折腾 vux-loader : )。
生命在于折腾,为了实现上边说的 $t 在 webpack 层面的处理,分享一下我从 @vux/loader 里边拆解出来的 template-loader 哈。
vux-template-loader2.js
,内容如下,基本都是从 @vux/loader/src/template-loader
复制过来的,略有改动:// Modified by Riant rianthar@qq.com at 2019-11-20
// 主要是为了兼容 vue-cli 3 以及 Webpack 的一些变更对一些方法作一些不严谨的修改
// 未完全测试,不需要 i18n 的话,应该可以直接使用,否则请注意多测试
// 使用: vue.confg.js
// configureWebpack: config => {
// return {
// module: {
// rules: [{
// enforce: 'pre',
// test: /\.vue$/,
// resourceQuery: /^\?vue&type=template/,
// use: [{loader: './vux-template-loader2', options: {}}]
// }]
// }
// }
// }
'use strict'
const VuxLoaderPath = './node_modules/@vux/loader/'
const _ = require('lodash')
const touch = require('touch')
const utils = require('loader-utils')
const yamlReader = require('js-yaml')
const fs = require('fs')
const path = require('path')
const matchI18nReg = /\$t\('?(.*?)'?\)/g
const parseVirtualComponent = require(VuxLoaderPath + 'src/libs/parse-virtual-component')
const parseXIcon = require(VuxLoaderPath + 'src/libs/parse-x-icon')
const removeTagCode = require(VuxLoaderPath + 'src/libs/getTag').removeTagCode
const reserveTagCode = require(VuxLoaderPath + 'src/libs/getTag').reserveTagCode
// const compareVersions = require('compare-versions')
const i18nParser = require(VuxLoaderPath + 'libs/parse-i18n-function').parse
const getI18nBlockWithLocale = require(VuxLoaderPath + 'libs/get-i18n-block').getWithLocale
const getName = function (path) {
return path.replace(/\\/g, '/').split('components')[1].replace('index.vue', '').replace(/\//g, '')
}
const getLocals = (options) => {
const vuxLocalesContent = fs.readFileSync('./node_modules/vux/src/locales/all.yml', 'utf-8')
return yamlReader.safeLoad(vuxLocalesContent)
}
module.exports = function (source) {
const _this = this
this.cacheable()
// const query = this.query ? utils.parseQuery(this.query) : {}
let options = utils.getOptions(this)
let config = this.vux || options.vux || {options: {}, plugins: []}
const locales = this.vuxLocales || getLocals(options)
if (this['thread-loader']) {
const configFile = require.resolve('vux/.config.cache.json')
config = require(configFile)
}
const basename = path.basename(this.resourcePath)
let isVuxVueFile = this.resourcePath.replace(/\\/g, '/').indexOf('vux/src/components') > -1
let isVuxVueDemo = this.resourcePath.replace(/\\/g, '/').indexOf('vux/src/demos') > -1
if (config.options.vuxDev && this.resourcePath.replace(/\\/g, '/').indexOf('src/components') > -1) {
isVuxVueFile = true
}
if (config.options.vuxDev && this.resourcePath.replace(/\\/g, '/').indexOf('src/demos') > -1) {
isVuxVueDemo = true
}
// x-icon
if (config.options.useVuxUI && source.indexOf('<x-icon') > -1) {
source = parseXIcon(source, config)
}
// v-no-ssr
if (config.options.ssr) {
source = removeTagCode(source, 'v-no-ssr')
source = reserveTagCode(source, 'v-ssr')
} else {
source = removeTagCode(source, 'v-ssr')
source = reserveTagCode(source, 'v-no-ssr')
}
/**
* ======== i18n ========
*/
let dynamic = false
let locale = 'zh-CN'
let vuxFunctionName = '$t'
let functionName = '$t'
let staticTranslations = {}
let langs = ['en', 'zh-CN']
let staticReplace
// 如果不设置, dynamic 为false, local 为 zh-CN
const i18nPluginsMatch = config.plugins.filter(function (one) {
return one.name === 'i18n'
})
if (i18nPluginsMatch.length) {
dynamic = !i18nPluginsMatch[0].vuxStaticReplace
locale = i18nPluginsMatch[0].vuxLocale || 'zh-CN'
vuxFunctionName = i18nPluginsMatch[0].vuxFunctionName || '$t'
functionName = i18nPluginsMatch[0].functionName || '$t'
langs = i18nPluginsMatch[0].localeList || langs
staticTranslations = i18nPluginsMatch[0].staticTranslations || {}
staticReplace = typeof i18nPluginsMatch[0].staticReplace === 'undefined' ? undefined : i18nPluginsMatch[0].staticReplace
} else {
dynamic = false
locale = 'zh-CN'
vuxFunctionName = '$t'
}
if ((isVuxVueFile) && source.indexOf("$t(") > -1) {
const name = getName(this.resourcePath)
if (!dynamic) {
source = source.replace(matchI18nReg, function (a, b) {
let key = `vux.${name}.${b}`
if (a.indexOf('/*') > -1) {
const start = a.indexOf('/*')
const end = a.indexOf('*/')
const str = a.slice(start + 2, end - 1)
const map = {}
const list = str.split(',').map(one => {
one = one.trim()
const pair = one.split(':').map(one => one.trim())
map[pair[0]] = pair[1]
})
return map[locale]
}
if (a.indexOf("'") > -1) { // 用于翻译字符
return "'" + locales[locale][key] + "'"
} else { // 用于翻译变量,如 $t(text)
return b
}
})
} else {
// dynamic 为 true, 则对于 vux 源码,把 key 加上 prefix
source = source.replace(matchI18nReg, function (a, b) {
if (a.indexOf("'") > -1) {
return a.replace(b, `vux.${name}.${b}`).replace('$t', vuxFunctionName)
} else {
return a.replace('$t', vuxFunctionName)
}
})
}
} else if (!isVuxVueFile && source.indexOf(`${functionName}(`) > -1 && staticTranslations && staticReplace === true) {
// 对于项目文件进行静态替换
/**
let matchI18nReg = new RegExp(`\$${functionName}\('?(.*?)'?\)`, 'g')
source = source.replace(matchI18nReg, function (a, b) {
if (a.indexOf("'") > -1) {
return `${ staticTranslations[b] || b }`
}
})
*/
const rs = getI18nBlockWithLocale({
code: _this.resourcePath,
isFile: true,
locale: locale
})
source = i18nParser(source, rs)
}
config.plugins.forEach(function (plugin) {
// template-feature-switch
/**
<off feature="false"> show
<on feature="true"> show
<off feature="true"> hide
<on feature="false"> hide
*/
if (plugin.name === 'template-feature-switch') {
// replace features
if (plugin.features && source.indexOf('</on>') > -1) {
source = parseOnFeature(source, plugin.features)
}
if (plugin.features && source.indexOf('</off>') > -1) {
source = parseOffFeature(source, plugin.features)
}
}
// 非 vux 组件才需要生成语言
if (!isVuxVueFile && plugin.name === 'i18n' && plugin.extractToFiles) {
const savePath = path.resolve(config.options.projectRoot, plugin.extractToFiles)
let format = 'yml'
if (/\.json$/.test(plugin.extractToFiles)) {
format = 'json'
}
let fileMode = 'all'
if (plugin.extractToFiles.indexOf('{lang}') !== -1) {
fileMode = 'single'
}
const isDynamic = plugin.staticReplace === false
if (isDynamic) {
setTimeout(function () {
const rawFileContent = fs.readFileSync(_this.resourcePath, 'utf-8')
const results = rawFileContent.match(/<i18n[^>]*>([\s\S]*?)<\/i18n>/)
if (results) {
let attrsMap = {}
const attrs = results[0].split('\n')[0].replace('<i18n', '')
.replace('>', '')
.replace(/"/g, '')
.replace(/\r/g, '')
.split(' ')
.filter(function (one) {
return !!one
}).forEach(function (one) {
let tmp = one.split('=')
attrsMap[tmp[0]] = tmp[1]
})
try {
const local = yamlReader.safeLoad(results[1])
const rs = parseI18n(local, langs)
let finalConfig = {}
// all and yml format
if (fileMode === 'all' && format === 'yml') {
touch.sync(savePath)
let currentConfig = fs.readFileSync(savePath, 'utf-8')
if (!currentConfig) {
finalConfig = rs.translations
} else {
finalConfig = mergeLang(yamlReader.safeLoad(currentConfig), rs.translations)
}
if (!currentConfig || (currentConfig && JSON.stringify(yamlReader.safeLoad(currentConfig)) !== JSON.stringify(finalConfig))) {
fs.writeFileSync(savePath, yamlReader.safeDump(finalConfig))
}
}
if (fileMode === 'all' && format === 'json') {
touch.sync(savePath)
let currentConfig = fs.readFileSync(savePath, 'utf-8')
if (!currentConfig) {
finalConfig = rs.translations
} else {
finalConfig = mergeLang(JSON.parse(currentConfig), rs.translations)
}
if (!currentConfig || (currentConfig && JSON.stringify(currentConfig) !== JSON.stringify(finalConfig))) {
fs.writeFileSync(savePath, JSON.stringify(finalConfig, null, 2))
}
}
// single and yml
if (fileMode === 'single' && format === 'yml') {
for (let i = 0; i < langs.length; i++) {
let lang = langs[i]
let savePath = path.resolve(config.options.projectRoot, plugin.extractToFiles).replace('{lang}', lang)
touch.sync(savePath)
let currentConfig = fs.readFileSync(savePath, 'utf-8')
if (!currentConfig) {
finalConfig = rs.translations[lang]
} else {
finalConfig = Object.assign(yamlReader.safeLoad(currentConfig), rs.translations[lang])
}
if (!currentConfig || (currentConfig && JSON.stringify(yamlReader.safeLoad(currentConfig)) !== JSON.stringify(finalConfig))) {
fs.writeFileSync(savePath, yamlReader.safeDump(finalConfig))
}
}
}
if (fileMode === 'single' && format === 'json') {
for (let i = 0; i < langs.length; i++) {
let lang = langs[i]
let savePath = path.resolve(config.options.projectRoot, plugin.extractToFiles).replace('{lang}', lang)
touch.sync(savePath)
let currentConfig = fs.readFileSync(savePath, 'utf-8')
if (!currentConfig) {
finalConfig = rs.translations[lang]
} else {
finalConfig = Object.assign(JSON.parse(currentConfig), rs.translations[lang])
}
if (!currentConfig || (currentConfig && JSON.stringify(currentConfig) !== JSON.stringify(finalConfig))) {
fs.writeFileSync(savePath, JSON.stringify(finalConfig, null, 2))
}
}
}
} catch (e) {
console.log(e)
console.log('yml 格式有误,请重新检查')
}
}
})
}
}
// template-parser
if (plugin.name === 'template-parser') {
if (plugin.fn) {
source = plugin.fn.call(_this, source)
}
if (plugin.replaceList && plugin.replaceList.length) {
plugin.replaceList.forEach(function (replacer) {
source = source.replace(replacer.test, replacer.replaceString)
})
}
}
if (plugin.name === 'template-string-append') {
if (new RegExp(plugin.test).test(_this.resourcePath)) {
var componentName = basename.replace('.vue', '').toLowerCase()
var string = plugin.fn({
resourcePath: _this.resourcePath,
basename: basename
})
if (string) {
source = source.replace(/\s+$/g, '').replace(/\\n/g, '').replace(/<\/div>$/, string + '</div>')
}
}
}
})
if (config.options.vuxWriteFile === true) {
fs.writeFileSync(this.resourcePath + '.vux.html', source)
}
source = removeTagCode(source, 'i18n')
return source
}
function parseOnFeature(content, features) {
content = content.replace(/<on[^>]*>([\s\S]*?)<\/on>/g, function (tag, text) {
const key = tag.split('\n')[0].replace('<on', '')
.replace('>', '')
.replace(/"/g, '')
.replace(/\r/g, '')
.split(' ')
.filter(function (one) {
return !!one
}).map(function (one) {
let tmp = one.split('=')
return tmp[1]
})
if (features[key] && features[key] === true) {
// true
return text
} else {
// false
return ''
}
})
return content
}
function parseOffFeature(content, features) {
content = content.replace(/<off[^>]*>([\s\S]*?)<\/off>/g, function (tag, text) {
const key = tag.split('\n')[0].replace('<off', '')
.replace('>', '')
.replace(/"/g, '')
.replace(/\r/g, '')
.split(' ')
.filter(function (one) {
return !!one
}).map(function (one) {
let tmp = one.split('=')
return tmp[1]
})
if (!features[key]) {
// false
return text
} else {
// true
return ''
}
})
return content
}
function parseI18n(json, langs) {
langs = langs || []
if (!langs || !langs.length) {
for (let i in json) {
langs = langs.concat(Object.keys(json[i]))
}
langs = _.uniq(langs)
}
let rs = {}
for (let i = 0; i < langs.length; i++) {
let lang = langs[i]
if (!rs[lang]) {
rs[lang] = {}
}
for (let j in json) {
rs[lang][j] = json[j][lang] || j
}
}
return {
langs: langs,
translations: rs
}
}
function mergeLang(a, b) {
for (let i in b) {
for (let j in b[i]) {
if (!a[i]) {
a[i] = b[i]
}
a[i][j] = b[i][j]
}
}
return a
}
const getLessVariables = require('./node_modules/@vux/loader/src/libs/get-less-variables')
module.exports = {
configureWebpack: config => {
return {
module: {
rules: [{
enforce: 'pre',
test: /.vue$/,
resourceQuery: /^\?vue&type=template/,
use: [{loader: './vux-template-loader2', options: {}}] // here
}]
}
}
},
css: {
loaderOptions: {
// 可能有同样使用 scss 的同学需要,可以参考使用哈
// sass: {
// prependData: @import "@/assets/style/_setting.scss";
// 注意此处路径
// },
less: {
modifyVars: getLessVariables('./src/assets/style/theme.less') // here
},
}
},
}
这样的话,$t is not defined 之类的问题应该也就解决了;预计 XIcon 应该也可以使用了;其他的特性大家有用到的话,测试了看哈。
这么一折腾的话,其实整个 loader 大概的一些问题也就清楚了,只是其他文件可能还有一些 webpack 及其周边的一些方法库调用方式没有跟上版本可能需要调整,应该可以尝试处理一下 @vux/loader 了。后边看有时间的话试试。
有需要的同学暂时可以将着上述的方法试试哈,如有修改的不严谨的地方,欢迎补充。
@iamziyue 把vux在main.js全局引用就行,不要单独在vue组件里面引用。
我是老项目版本升级的cli3,我自己没事就折腾一下,暂时不敢发正式。和你出现了一样的问题,请问你解决了吗? main.js里面引入不好办,还有其他方法吗? 全都是这样的。。。,看了一下午的这片文章了,现在在试@Riant 这老哥的代码
cli3升级cli4后,报错 // 我使用这行代码会出现(如果你看到这一行,说明 vux-loader 配置有问题或者代码书写规范的原因导致无法解析成按需引入组件,会导致打包体积过大。请升级到最新版本 vux-loader,建议开启 eslint(standard)。)的提示
看了一圈,每个方案都试了一遍,可以决定放弃这个框架了。以后不要用这种侵入式配置的框架
@flicat 抱歉啊,确实都没有时间做大改动。
我找到别的办法引入了。
补充一下Riant的回答: https://github.com/airyland/vux/issues/3430#issuecomment-555375644
如果出现如下错误
Syntax Error: ValidationError: Invalid options object. Less Loader has been initialized using an options object that does not match the API schema.
options has an unknown property 'modifyVars'. These properties are valid: object { lessOptions?, additionalData?, sourceMap?, webpackImporter?, implementation? }
若 less-loader 版本< 6.0,则vue.config.js选项为
css: {
loaderOptions: {
less: {
javascriptEnabled: true,
modifyVars: getLessVariables('./src/assets/style/vux_theme.less')
}
}
},
若 less-loader 版本>= 6.0
css: {
loaderOptions: {
less: {
lessOptions: {
javascriptEnabled: true,
modifyVars: getLessVariables('./src/assets/style/vux_theme.less')
}
}
}
},