elementUI源码学习
学习笔记。
最近在看element的table表格优化,又去看了一下element源码框架。element 的架构是很优秀,通过大量的脚本实现工程化,让组件库的开发者专注于事情本身,比如新加组件,一键生成组件所有文件,并完成这些文件基本结构的编写和相关的引入配置,而开发者只需完成组件定义就行了。
element 的工程化由5个部分:build 目录下的工程化配置和脚本、eslint、travis ci、Makefile、package.json 的 scripts。
当前主要总结学习一下build 目录下的工程化配置和脚本。
build
build 目录存放工程化相关配置和脚本。比如 /build/bin 目录下的 JS 脚本让组件库开发者专注于组件的开发,除此之外不需要管其他任何事情;
build/md-loader 是官网组件页面根据 markdown 实现组件 demo + 文档 的关键;还有比如持续集成、webpack 配置等,接下来就详细介绍这些配置和脚本。
build/bin/build-entry.js
组件配置文件(components.json)结合字符串模版库,自动生成 /src/index.js 文件,避免每次新增组件时手动在 /src/index.js 中引入和导出组件。
/*** 生成 /src/index.js* 1、自动导入组件库所有组件* 2、定义全量注册组件库组件的 install 方法* 3、导出版本、install、各个组件*/// key 为包名、路径为值
var Components = require('../../components.json');
var fs = require('fs');
// 模版库
var render = require('json-templater/string');
// 负责将 comp-name 形式的字符串转换为 CompName
var uppercamelcase = require('uppercamelcase');
var path = require('path');
var endOfLine = require('os').EOL;// 输出路径 /src/index.js
var OUTPUT_PATH = path.join(__dirname, '../../src/index.js');
// 导入模版,import CompName from '../packages/comp-name/index.js'
var IMPORT_TEMPLATE = 'import {{name}} from \'../packages/{{package}}/index.js\';';
// ' CompName'
var INSTALL_COMPONENT_TEMPLATE = ' {{name}}';
// /src/index.js 的模版
var MAIN_TEMPLATE = `/* Automatically generated by './build/bin/build-entry.js' */{{include}}
import locale from 'element-ui/src/locale';
import CollapseTransition from 'element-ui/src/transitions/collapse-transition';const components = [
{{install}},CollapseTransition
];const install = function(Vue, opts = {}) {locale.use(opts.locale);locale.i18n(opts.i18n);components.forEach(component => {Vue.component(component.name, component);});Vue.use(InfiniteScroll);Vue.use(Loading.directive);Vue.prototype.$ELEMENT = {size: opts.size || '',zIndex: opts.zIndex || 2000};Vue.prototype.$loading = Loading.service;Vue.prototype.$msgbox = MessageBox;Vue.prototype.$alert = MessageBox.alert;Vue.prototype.$confirm = MessageBox.confirm;Vue.prototype.$prompt = MessageBox.prompt;Vue.prototype.$notify = Notification;Vue.prototype.$message = Message;};/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {install(window.Vue);
}export default {version: '{{version}}',locale: locale.use,i18n: locale.i18n,install,CollapseTransition,Loading,
{{list}}
};
`;delete Components.font;// 得到所有的包名,[comp-name1, comp-name2]
var ComponentNames = Object.keys(Components);// 存放所有的 import 语句
var includeComponentTemplate = [];
// 组件名数组
var installTemplate = [];
// 组件名数组
var listTemplate = [];// 遍历所有的包名
ComponentNames.forEach(name => {// 将连字符格式的包名转换成大驼峰形式,就是组件名,比如 form-item =》 FormItemvar componentName = uppercamelcase(name);// 替换导入语句中的模版变量,生成导入语句,import FromItem from '../packages/form-item/index.js'includeComponentTemplate.push(render(IMPORT_TEMPLATE, {name: componentName,package: name}));// 这些组件从 components 数组中剔除,不需要全局注册,采用挂载到原型链的方式,在模版字符串的 install 方法中有写if (['Loading', 'MessageBox', 'Notification', 'Message', 'InfiniteScroll'].indexOf(componentName) === -1) {installTemplate.push(render(INSTALL_COMPONENT_TEMPLATE, {name: componentName,component: name}));}// 将所有的组件放到 listTemplates,最后导出if (componentName !== 'Loading') listTemplate.push(` ${componentName}`);
});// 替换模版中的四个变量
var template = render(MAIN_TEMPLATE, {include: includeComponentTemplate.join(endOfLine),install: installTemplate.join(',' + endOfLine),version: process.env.VERSION || require('../../package.json').version,list: listTemplate.join(',' + endOfLine)
});// 将就绪的模版写入 /src/index.js
fs.writeFileSync(OUTPUT_PATH, template);
console.log('[build entry] DONE:', OUTPUT_PATH);
`
/build/bin/build-locale.js
通过 babel 将 ES Module 风格的所有翻译文件(/src/locale/lang)转译成 UMD 风格。
/*** 通过 babel 将 ES Module 风格的翻译文件转译成 UMD 风格*/
var fs = require('fs');
var save = require('file-save');
var resolve = require('path').resolve;
var basename = require('path').basename;// 翻译文件目录,这些文件用于官网
var localePath = resolve(__dirname, '../../src/locale/lang');
// 得到目录下的所有翻译文件
var fileList = fs.readdirSync(localePath);// 转换函数
var transform = function(filename, name, cb) {require('babel-core').transformFile(resolve(localePath, filename), {plugins: ['add-module-exports',['transform-es2015-modules-umd', {loose: true}]],moduleId: name}, cb);
};// 遍历所有文件
fileList// 只处理 js 文件,其实目录下不存在非 js 文件.filter(function(file) {return /\.js$/.test(file);}).forEach(function(file) {var name = basename(file, '.js');// 调用转换函数,将转换后的代码写入到 lib/umd/locale 目录下transform(file, name, function(err, result) {if (err) {console.error(err);} else {var code = result.code;code = code.replace('define(\'', 'define(\'element/locale/').replace('global.', 'global.ELEMENT.lang = global.ELEMENT.lang || {}; \n global.ELEMENT.lang.');save(resolve(__dirname, '../../lib/umd/locale', file)).write(code);console.log(file);}});});
/build/bin/gen-cssfile.js
自动在 /packages/theme-chalk/src/index.scss|css 中引入各个组件包的样式,在全量注册组件库时需要用到这个样式文件,即 import 'packages/theme-chalk/src/index.scss。
/*** 自动在 /packages/theme-chalk/src/index.scss|css 中引入各个组件包的样式* 在全量注册组件库时需要用到该样式文件,即 import 'packages/theme-chalk/src/index.scss*/
var fs = require('fs');
var path = require('path');
var Components = require('../../components.json');
var themes = ['theme-chalk'
];
// 得到所有的包名
Components = Object.keys(Components);
// 所有组件包的基础路径,/packages
var basepath = path.resolve(__dirname, '../../packages/');// 判断指定文件是否存在
function fileExists(filePath) {try {return fs.statSync(filePath).isFile();} catch (err) {return false;}
}// 遍历所有组件包,生成引入所有组件包样式的 import 语句,然后自动生成 packages/theme-chalk/src/index.scss|css 文件
themes.forEach((theme) => {// 是否是 scss,element-ui 默认使用 scss 编写样式var isSCSS = theme !== 'theme-default';// 导入基础样式文件 @import "./base.scss|css";\nvar indexContent = isSCSS ? '@import "./base.scss";\n' : '@import "./base.css";\n';// 遍历所有组件包,并生成 @import "./comp-package.scss|css";\nComponents.forEach(function(key) {// 跳过这三个组件包if (['icon', 'option', 'option-group'].indexOf(key) > -1) return;// comp-package.scss|cssvar fileName = key + (isSCSS ? '.scss' : '.css');// 导入语句,@import "./comp-package.scss|css";\nindexContent += '@import "./' + fileName + '";\n';// 如果该组件包的样式文件不存在,比如 /packages/form-item/theme-chalk/src/form-item.scss 不存在,则认为其被遗漏了,创建该文件var filePath = path.resolve(basepath, theme, 'src', fileName);if (!fileExists(filePath)) {fs.writeFileSync(filePath, '', 'utf8');console.log(theme, ' 创建遗漏的 ', fileName, ' 文件');}});// 生成 /packages/theme-chalk/src/index.scss|css,负责引入所有组件包的样式fs.writeFileSync(path.resolve(basepath, theme, 'src', isSCSS ? 'index.scss' : 'index.css'), indexContent);
});
/build/bin/i18n.js
根据模版(/examples/pages/template)生成四种语言的官网页面的 .vue 文件。
'use strict';var fs = require('fs');
var path = require('path');
// 官网页面翻译配置,内置了四种语言
var langConfig = require('../../examples/i18n/page.json');// 遍历所有语言
langConfig.forEach(lang => {// 创建 /examples/pages/{lang},比如: /examples/pages/zh-CNtry {fs.statSync(path.resolve(__dirname, `../../examples/pages/${ lang.lang }`));} catch (e) {fs.mkdirSync(path.resolve(__dirname, `../../examples/pages/${ lang.lang }`));}// 遍历所有的页面,根据 page.tpl 自动生成对应语言的 .vue 文件Object.keys(lang.pages).forEach(page => {// 比如 /examples/pages/template/index.tplvar templatePath = path.resolve(__dirname, `../../examples/pages/template/${ page }.tpl`);// /examples/pages/zh-CN/index.vuevar outputPath = path.resolve(__dirname, `../../examples/pages/${ lang.lang }/${ page }.vue`);// 读取模版文件var content = fs.readFileSync(templatePath, 'utf8');// 读取 index 页面的所有键值对的配置var pairs = lang.pages[page];// 遍历这些键值对,通过正则匹配的方式替换掉模版中对应的 keyObject.keys(pairs).forEach(key => {content = content.replace(new RegExp(`<%=\\s*${ key }\\s*>`, 'g'), pairs[key]);});// 将替换后的内容写入 vue 文件fs.writeFileSync(outputPath, content);});
});
/build/bin/iconInit.js
根据 icon.scss 样式文件中的选择器,通过正则匹配的方式,匹配出所有的 icon 名称,然后将这些 icon 名组成数组,将数组写入到 /examples/icon.json 文件中,该文件在官网的 icon 图标页用来自动生成所有的 icon 图标。
'use strict';/*** 根据 icon.scss 样式文件中的选择器,通过正则匹配的方式,匹配出所有的 icon 名称,* 然后将所有 icon 名组成的数组写入到 /examples/icon.json 文件中* 该文件在官网的 icon 图标页用来自动生成所有的 icon 图标*/
var postcss = require('postcss');
var fs = require('fs');
var path = require('path');
// icon.scss 文件内容
var fontFile = fs.readFileSync(path.resolve(__dirname, '../../packages/theme-chalk/src/icon.scss'), 'utf8');
// 得到样式节点
var nodes = postcss.parse(fontFile).nodes;
var classList = [];// 遍历所有的样式节点
nodes.forEach((node) => {// 从选择器中匹配出 icon 名称,比如 el-icon-add,匹配得到 addvar selector = node.selector || '';var reg = new RegExp(/\.el-icon-([^:]+):before/);var arr = selector.match(reg);// 将 icon 名称写入数组,if (arr && arr[1]) {classList.push(arr[1]);}
});classList.reverse(); // 希望按 css 文件顺序倒序排列// 将 icon 名组成的数组写入 /examples/icon.json 文件
fs.writeFile(path.resolve(__dirname, '../../examples/icon.json'), JSON.stringify(classList), () => {});
/build/bin/new-lang.js
为组件库添加新语言,比如 fr(法语),分别为涉及到的文件(components.json、page.json、route.json、nav.config.json、docs)设置该语言的相关配置,具体的配置项默认为英语,你只需要在相应的文件中将这些英文配置项翻译为对应的语言即可。
'use strict';/*** 为组件库添加新语言,比如 fr(法语)* 分别为涉及到的文件(components.json、page.json、route.json、nav.config.json、docs)设置该语言的相关配置* 具体的配置项默认为英语,你只需要在相应的文件中将这些英文配置项翻译为对应的语言即可*/console.log();
process.on('exit', () => {console.log();
});if (!process.argv[2]) {console.error('[language] is required!');process.exit(1);
}var fs = require('fs');
const path = require('path');
const fileSave = require('file-save');
const lang = process.argv[2];
// const configPath = path.resolve(__dirname, '../../examples/i18n', lang);// 添加到 components.json
const componentFile = require('../../examples/i18n/component.json');
if (componentFile.some(item => item.lang === lang)) {console.error(`${lang} already exists.`);process.exit(1);
}
let componentNew = Object.assign({}, componentFile.filter(item => item.lang === 'en-US')[0], { lang });
componentFile.push(componentNew);
fileSave(path.join(__dirname, '../../examples/i18n/component.json')).write(JSON.stringify(componentFile, null, ' '), 'utf8').end('\n');// 添加到 page.json
const pageFile = require('../../examples/i18n/page.json');
// 新语言的默认配置为英语,你只需要去 page.json 中将该语言配置中的应为翻译为该语言即可
let pageNew = Object.assign({}, pageFile.filter(item => item.lang === 'en-US')[0], { lang });
pageFile.push(pageNew);
fileSave(path.join(__dirname, '../../examples/i18n/page.json')).write(JSON.stringify(pageFile, null, ' '), 'utf8').end('\n');// 添加到 route.json
const routeFile = require('../../examples/i18n/route.json');
routeFile.push({ lang });
fileSave(path.join(__dirname, '../../examples/i18n/route.json')).write(JSON.stringify(routeFile, null, ' '), 'utf8').end('\n');// 添加到 nav.config.json
const navFile = require('../../examples/nav.config.json');
navFile[lang] = navFile['en-US'];
fileSave(path.join(__dirname, '../../examples/nav.config.json')).write(JSON.stringify(navFile, null, ' '), 'utf8').end('\n');// docs 下新建对应文件夹
try {fs.statSync(path.resolve(__dirname, `../../examples/docs/${ lang }`));
} catch (e) {fs.mkdirSync(path.resolve(__dirname, `../../examples/docs/${ lang }`));
}console.log('DONE!');
/build/bin/new.js
为组件库添加新组件时会使用该脚本,一键生成组件所有文件并完成这些文件基本结构的编写和相关的引入配置,总共涉及 13 个文件的添加和改动,比如:make new city 城市列表。该脚本的存在,让你为组件库开发新组件时,只需专注于组件代码的编写即可,其它的一概不用管。
'use strict';/*** 添加新组件* 比如:make new city 城市列表* 1、在 /packages 目录下新建组件目录,并完成目录结构的创建* 2、创建组件文档,/examples/docs/{lang}/city.md* 3、创建组件单元测试文件,/test/unit/specs/city.spec.js* 4、创建组件样式文件,/packages/theme-chalk/src/city.scss* 5、创建组件类型声明文件,/types/city.d.ts* 6、配置* 在 /components.json 文件中配置组件信息* 在 /examples/nav.config.json 中添加该组件的路由配置* 在 /packages/theme-chalk/src/index.scss 文件中自动引入该组件的样式文件* 将类型声明文件在 /types/element-ui.d.ts 中自动引入* 总之,该脚本的存在,让你只需专注于编写你的组件代码,其它的一概不用管*/console.log();
process.on('exit', () => {console.log();
});if (!process.argv[2]) {console.error('[组件名]必填 - Please enter new component name');process.exit(1);
}const path = require('path');
const fs = require('fs');
const fileSave = require('file-save');
const uppercamelcase = require('uppercamelcase');
// 组件名称,比如 city
const componentname = process.argv[2];
// 组件的中文名称
const chineseName = process.argv[3] || componentname;
// 将组件名称转换为大驼峰形式,city => City
const ComponentName = uppercamelcase(componentname);
// 组件包目录,/packages/city
const PackagePath = path.resolve(__dirname, '../../packages', componentname);
// 需要添加的文件列表和文件内容的基本结构
const Files = [// /packages/city/index.js{filename: 'index.js',// 文件内容,引入组件,定义组件静态方法 install 用来注册组件,然后导出组件content: `import ${ComponentName} from './src/main';/* istanbul ignore next */
${ComponentName}.install = function(Vue) {Vue.component(${ComponentName}.name, ${ComponentName});
};export default ${ComponentName};`},// 定义组件的基本结构,/packages/city/src/main.vue{filename: 'src/main.vue',// 文件内容,sfccontent: `<template><div class="el-${componentname}"></div>
</template><script>
export default {name: 'El${ComponentName}'
};
</script>`},// 四种语言的文档,/examples/docs/{lang}/city.md,并设置文件标题{filename: path.join('../../examples/docs/zh-CN', `${componentname}.md`),content: `## ${ComponentName} ${chineseName}`},{filename: path.join('../../examples/docs/en-US', `${componentname}.md`),content: `## ${ComponentName}`},{filename: path.join('../../examples/docs/es', `${componentname}.md`),content: `## ${ComponentName}`},{filename: path.join('../../examples/docs/fr-FR', `${componentname}.md`),content: `## ${ComponentName}`},// 组件测试文件,/test/unit/specs/city.spec.js{filename: path.join('../../test/unit/specs', `${componentname}.spec.js`),// 文件内容,给出测试文件的基本结构content: `import { createTest, destroyVM } from '../util';
import ${ComponentName} from 'packages/${componentname}';describe('${ComponentName}', () => {let vm;afterEach(() => {destroyVM(vm);});it('create', () => {vm = createTest(${ComponentName}, true);expect(vm.$el).to.exist;});
});
`},// 组件样式文件,/packages/theme-chalk/src/city.scss{filename: path.join('../../packages/theme-chalk/src', `${componentname}.scss`),// 文件基本结构content: `@import "mixins/mixins";
@import "common/var";@include b(${componentname}) {
}`},// 组件类型声明文件{filename: path.join('../../types', `${componentname}.d.ts`),// 类型声明文件基本结构content: `import { ElementUIComponent } from './component'/** ${ComponentName} Component */
export declare class El${ComponentName} extends ElementUIComponent {
}`}
];// 将组件添加到 components.json,{ City: './packages/city/index.js' }
const componentsFile = require('../../components.json');
if (componentsFile[componentname]) {console.error(`${componentname} 已存在.`);process.exit(1);
}
componentsFile[componentname] = `./packages/${componentname}/index.js`;
fileSave(path.join(__dirname, '../../components.json')).write(JSON.stringify(componentsFile, null, ' '), 'utf8').end('\n');// 将组件样式文件在 index.scss 中引入
const sassPath = path.join(__dirname, '../../packages/theme-chalk/src/index.scss');
const sassImportText = `${fs.readFileSync(sassPath)}@import "./${componentname}.scss";`;
fileSave(sassPath).write(sassImportText, 'utf8').end('\n');// 将组件的类型声明文件在 element-ui.d.ts 中引入
const elementTsPath = path.join(__dirname, '../../types/element-ui.d.ts');let elementTsText = `${fs.readFileSync(elementTsPath)}
/** ${ComponentName} Component */
export class ${ComponentName} extends El${ComponentName} {}`;const index = elementTsText.indexOf('export') - 1;
const importString = `import { El${ComponentName} } from './${componentname}'`;elementTsText = elementTsText.slice(0, index) + importString + '\n' + elementTsText.slice(index);fileSave(elementTsPath).write(elementTsText, 'utf8').end('\n');// 遍历 Files 数组,创建列出的所有文件并写入文件内容
Files.forEach(file => {fileSave(path.join(PackagePath, file.filename)).write(file.content, 'utf8').end('\n');
});// 在 nav.config.json 中添加新组件对应的路由配置
const navConfigFile = require('../../examples/nav.config.json');// 遍历配置中的各个语言,在所有语言配置中都增加该组件的路由配置
Object.keys(navConfigFile).forEach(lang => {let groups = navConfigFile[lang][4].groups;groups[groups.length - 1].list.push({path: `/${componentname}`,title: lang === 'zh-CN' && componentname !== chineseName? `${ComponentName} ${chineseName}`: ComponentName});
});fileSave(path.join(__dirname, '../../examples/nav.config.json')).write(JSON.stringify(navConfigFile, null, ' '), 'utf8').end('\n');console.log('DONE!');
/build/bin/template.js
监听 /examples/pages/template 目录下的所有模版文件,当模版文件发生改变时自动执行 npm run i18n,即执行 i18n.js 脚本,重新生成四种语言的 .vue 文件。
/*** 监听 /examples/pages/template 目录下的所有模版文件,当模版文件发生改变时自动执行 npm run i18n,* 即执行 i18n.js 脚本,重新生成四种语言的 .vue 文件*/const path = require('path');
// 监听目录
const templates = path.resolve(process.cwd(), './examples/pages/template');// 负责监听的库
const chokidar = require('chokidar');
// 监听模板目录
let watcher = chokidar.watch([templates]);// 当目录下的文件发生改变时,自动执行 npm run i18n
watcher.on('ready', function() {watcher.on('change', function() {exec('npm run i18n');});
});// 负责执行命令
function exec(cmd) {return require('child_process').execSync(cmd).toString().trim();
}
/build/bin/version.js
根据 /package.json 文件,自动生成 /examples/version.json,用于记录组件库的版本信息,这些版本洗洗在官网组件页面的头部导航栏会用到。
/*** 根据 package.json 自动生成 /examples/version.json,用于记录组件库的版本信息* 这些版本信息在官网组件页面的头部导航栏会用到*/
var fs = require('fs');
var path = require('path');
var version = process.env.VERSION || require('../../package.json').version;
var content = { '1.4.13': '1.4', '2.0.11': '2.0', '2.1.0': '2.1', '2.2.2': '2.2', '2.3.9': '2.3', '2.4.11': '2.4', '2.5.4': '2.5', '2.6.3': '2.6', '2.7.2': '2.7', '2.8.2': '2.8', '2.9.2': '2.9', '2.10.1': '2.10', '2.11.1': '2.11', '2.12.0': '2.12', '2.13.2': '2.13', '2.14.1': '2.14' };
if (!content[version]) content[version] = '2.15';
fs.writeFileSync(path.resolve(__dirname, '../../examples/versions.json'), JSON.stringify(content));
/build/md-loader
它是一个 loader,官网组件页面的 组件 demo + 文档的模式一大半的功劳都是源自于它。
可以在 /examples/route.config.js 中看到 registerRoute 方法生成组件页面的路由配置时,使用 loadDocs 方法加载/examples/docs/{lang}/comp.md 。注意,这里加载的 markdown 文档,而不是平时常见的 vue 文件,但是却能想 vue 文件一样在页面上渲染成一个 Vue 组件,这是怎么做到的呢?
我们知道,webpack 的理念是一切资源都可以 require,只需配置相应的 loader 即可。在 /build/webpack.demo.js 文件中的 module.rules 下可以看到对 markdow(.md) 规则的处理,先通过 md-loader 处理 markdown 文件,从中解析出 vue 代码,然后交给 vue-loader,最终生成 sfc(vue 单文件组件)渲染到页面。这就能看到组件页面的文档 + 组件 demo 展示效果。
{test: /\.md$/,use: [{loader: 'vue-loader',options: {compilerOptions: {preserveWhitespace: false}}},{loader: path.resolve(__dirname, './md-loader/index.js')}]
}
如果对 loader 的具体实现感兴趣可以自行深入阅读。
/build/config.js
webpack 的公共配置,比如 externals、alias 等。通过 externals 的配置解决了组件库部分代码的冗余问题,比如组件和组件库公共模块的代码,但是组件样式冗余问题没有得到解决;alias 别名配置为开发组件库提供了方便。
/*** webpack 公共配置,比如 externals、alias*/
var path = require('path');
var fs = require('fs');
var nodeExternals = require('webpack-node-externals');
var Components = require('../components.json');var utilsList = fs.readdirSync(path.resolve(__dirname, '../src/utils'));
var mixinsList = fs.readdirSync(path.resolve(__dirname, '../src/mixins'));
var transitionList = fs.readdirSync(path.resolve(__dirname, '../src/transitions'));
/*** externals 解决组件依赖其它组件并按需引入时代码冗余的问题* 比如 Table 组件依赖 Checkbox 组件,在项目中如果我同时引入 Table 和 Checkbox 时,会不会产生冗余代码* 如果没有以下内容的的话,会,这时候你会看到有两份 Checkbox 组件代码。* 包括 locale、utils、mixins、transitions 这些公共内容,也会出现冗余代码* 但有了 externals 的设置,就会将告诉 webpack 不需要将这些 import 的包打包到 bundle 中,运行时再从外部去* 获取这些扩展依赖。这样就可以在打包后 /lib/tables.js 中看到编译后的 table.js 对 Checkbox 组件的依赖引入:* module.exports = require("element-ui/lib/checkbox")* 这么处理之后就不会出现冗余的 JS 代码,但是对于 CSS 部分,element-ui 并未处理冗余情况。* 可以看到 /lib/theme-chalk/table.css 和 /lib/theme-chalk/checkbox.css 中都有 Checkbox 组件的样式*/
var externals = {};Object.keys(Components).forEach(function(key) {externals[`element-ui/packages/${key}`] = `element-ui/lib/${key}`;
});externals['element-ui/src/locale'] = 'element-ui/lib/locale';
utilsList.forEach(function(file) {file = path.basename(file, '.js');externals[`element-ui/src/utils/${file}`] = `element-ui/lib/utils/${file}`;
});
mixinsList.forEach(function(file) {file = path.basename(file, '.js');externals[`element-ui/src/mixins/${file}`] = `element-ui/lib/mixins/${file}`;
});
transitionList.forEach(function(file) {file = path.basename(file, '.js');externals[`element-ui/src/transitions/${file}`] = `element-ui/lib/transitions/${file}`;
});externals = [Object.assign({vue: 'vue'
}, externals), nodeExternals()];exports.externals = externals;// 设置别名,方便使用
exports.alias = {main: path.resolve(__dirname, '../src'),packages: path.resolve(__dirname, '../packages'),examples: path.resolve(__dirname, '../examples'),'element-ui': path.resolve(__dirname, '../')
};exports.vue = {root: 'Vue',commonjs: 'vue',commonjs2: 'vue',amd: 'vue'
};exports.jsexclude = /node_modules|utils\/popper\.js|utils\/date\.js/;
相关文章:
elementUI源码学习
学习笔记。 最近在看element的table表格优化,又去看了一下element源码框架。element 的架构是很优秀,通过大量的脚本实现工程化,让组件库的开发者专注于事情本身,比如新加组件,一键生成组件所有文件,并完成…...
【LeetCode 热题 100】搜索插入位置 / 搜索旋转排序数组 / 寻找旋转排序数组中的最小值
⭐️个人主页:小羊 ⭐️所属专栏:LeetCode 热题 100 很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~ 目录 搜索插入位置搜索二维矩阵在排序数组中查找元素的第一个和最后一个位置搜索旋转排序数组寻找旋转排序数组中的最小值…...
捌拾伍- 量子傅里叶变换 (3)
前期的内容在 捌拾叁- 量子傅里叶变换 前期的内容在 捌拾肆- 量子傅里叶变换 (2) 9. 之前的 之前的公式写错了! Markdown 的 KaTeX 真难用!!! 而且之前的公式是从 j1 – jn ,但量子计算都是从 0 开始的,…...
探索ISBN查询接口:为图书管理系统赋能
在开发图书管理应用时,ISBN(国际标准书号)查询接口是获取图书元数据的核心工具。通过扫描图书条形码得到ISBN,再调用API即可轻松获取书名、作者、出版社、封面等信息。本文详细介绍几种主流ISBN查询API,包括国际和国内…...
Linux 内核中 inet_accept 的实现与自定义传输协议优化
在 Linux 内核中,网络协议栈的核心功能由一系列精心设计的函数实现,其中 inet_accept 是 TCP 协议接受新连接的关键入口。本文将深入分析该函数的实现逻辑,并探讨在实现自定义传输协议时如何权衡性能优化与代码简化。 一、inet_accept 函数解析 1. 功能概述 inet_accept 是…...
SAP-ABAP:SAP DMS(文档管理系统)的详细说明,涵盖其核心功能、架构、配置及实际应用
1. DMS 概述 SAP DMS(Document Management System)是SAP系统中用于管理企业文档的核心模块,支持文档的全生命周期管理(创建、存储、版本控制、审批、归档)。它与其他模块(如物料管理MM、生产计划PP、设备维…...
前端方法的总结及记录
个人简介 👨💻个人主页: 魔术师 📖学习方向: 主攻前端方向,正逐渐往全栈发展 🚴个人状态: 研发工程师,现效力于政务服务网事业 🇨🇳人生格言&…...
【Arthas实战】常见使用场景与命令分享
简介: Arthas是一款Java诊断工具,适用于多种场景,如接口响应变慢、CPU占用过高、热更新需求等。其核心命令包括实时监控面板(dashboard)、线程状态查看(thread)、方法调用链路追踪(trace&#x…...
SearchClassUtil
路径扫描工具SearchClassUtil,用于扫描指定包(XXXX)下的所有.class文件,并将它们的全限定类名(如tomcat.SearchClassUtil)收集到列表中返回。该工具使用递归文件遍历和反射机制,是实现 Spring 框…...
开放世界地形渲染:以三角洲行动为例(下篇)
本文主要介绍如何提升室外画面渲染的品质 版权声明 本文为“优梦创客”原创文章,您可以自由转载,但必须加入完整的版权声明文章内容不得删减、修改、演绎本文视频版本:见文末 渲染品质提升 要提升画面的品质,就是去提升渲染的画…...
GpuGeek 网络加速:破解 AI 开发中的 “最后一公里” 瓶颈
摘要: 网络延迟在AI开发中常被忽视,却严重影响效率。GpuGeek通过技术创新,提供学术资源访问和跨国数据交互的加速服务,助力开发者突破瓶颈。 目录 一、引言:当算力不再稀缺,网络瓶颈如何破局? …...
关于 Web安全:1. Web 安全基础知识
一、HTTP/HTTPS 协议详解 1. HTTP协议基础 什么是 HTTP? HTTP(HyperText Transfer Protocol)是互联网中浏览器和服务器之间传输数据的协议,基于请求-响应模式。它是一个无状态协议,意思是每次请求都是独立的&#x…...
debugfs:Linux 内核调试的利器
目录 一、什么是 debugfs?二、debugfs 的配置和启用方式2.1 内核配置选项2.2 挂载 debugfs2.3 Android 系统中的 debugfs 三、debugfs 的典型应用场景3.1 调试驱动开发3.2 内核子系统调试3.3 性能分析 四、常见 debugfs 子目录与功能示例4.1 /sys/kernel/debug/trac…...
Spyglass:跨时钟域同步(同步使能)
相关阅读 Spyglasshttps://blog.csdn.net/weixin_45791458/category_12828934.html?spm1001.2014.3001.5482 简介 同步使能方案主要用于数据信号跨时钟域同步,该方案将一个控制信号同步至目标时钟域并用其作为数据信号的捕获触发器的使能信号,如图1所示…...
安装Minikube
环境 CentOS7 参考 minikube start | minikube 创建虚拟机,参考 模拟Gitlab安装-CSDN博客 下载二进制包 curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 报错不能解析host,配置host 下载成功 安装 sudo install minikube-linux-am…...
图像锐化调整
一、背景介绍 之前找多尺度做对比度增强时候,发现了一些锐化相关算法,正好本来也要整理锐化,这里就直接顺手对之前做过的锐化大概整理了下,方便后续用的时候直接抓了。 这里整理的锐化主要是两块:一个是参考论文&#…...
【CanMV K230】AI_CUBE1.4
《k230-AI 最近小伙伴有做模型的需求。所以我重新捡起来了。正好把之前没测过的测一下。 这次我们用的是全新版本。AICUBE1.4.dotnet环境9.0 注意AICUBE训练模型对硬件有所要求。最好使用独立显卡。 有小伙伴说集显也可以。emmmm可以试试哈 集显显存2G很勉强了。 我们依然用…...
STM32外设AD-定时器触发 + DMA读取模板
STM32外设AD-定时器触发 DMA读取模板 一,方法思路二,定时器基础与配置1,定时器时钟源 (Clock Source)2,预分频器 (Prescaler - PSC)3,自动重装载寄存器 (Auto-Reload Register - ARR) / 周期 (Period)4,触…...
数据库故障排查指南:从入门到精通
1. 常见数据库故障类型 1.1 连接故障 数据库连接超时连接池耗尽网络连接中断认证失败1.2 性能故障 查询执行缓慢内存使用过高CPU使用率异常磁盘I/O瓶颈1.3 数据故障 数据不一致数据丢失数据损坏事务失败2. 故障排查流程 2.1 初步诊断 -- 检查数据库状态SHOW STATUS;SHOW PRO…...
【AT32】 AT32 移植 Freemodbus 主站
基于野火开发板 at32f437zgt6芯片 和at32 官方开发工具 移植了网上一套开源的freemodbus 主站 这里对modbus 协议不做过多的讲解 主要已实现代码为主 AT32 Work Bench 参考之前我之前的配置 与stm32cubemx软件差不多 注意485芯片的收发脚配置即可 AT32 IDE 说实话这软件太垃…...
内网环境下如何使用ntpdate实时同步时间
背景介绍 NTP(Network Time Protocol)是一种网络协议,用于同步计算机系统的时间。ntpdate是一个用于手动同步时间的命令行工具,它可以从指定的NTP服务器获取当前时间并更新本地系统时间。 ntpdate 服务介绍 功能:ntp…...
python版本管理工具-pyenv轻松切换多个Python版本
在使用python环境开发时,相信肯定被使用版本所烦恼,在用第三方库时依赖兼容的python版本不一样,有没有一个能同时安装多个python并能自由切换的工具呢,那就是pyenv,让你可以轻松切换多个Python 版本。 pyenv是什么 p…...
工商总局可视化模版 – 基于ECharts的大数据可视化HTML源码
概述 在大数据时代,数据可视化已成为各行各业进行数据分析和决策的重要工具。幽络源今天为大家带来一款基于ECharts的工商总局数据可视化HTML模版,帮助开发者快速搭建专业级工商广告数据展示平台。这款模版设计规范,功能完善,适合…...
计算机网络 : 网络基础
计算机网络 : 网络基础 目录 计算机网络 : 网络基础引言1. 网络发展背景2. 初始协议2.1 初始协议2.2 协议分层2.2.1 软件分层的好处2.2.2 OSI七层模型2.2.3 TCP/IP五层(四层)模型 2.3 TCP/IP协议2.3.1TCP/IP协议与操作系统的关系&…...
eSwitch manager 简介
eSwitch manager 的定义和作用 eSwitch manager 通常指的是能够配置和管理 eSwitch(嵌入式交换机)的实体或接口。在 NVIDIA/Mellanox 的网络架构中,Physical Function(PF)在 switchdev 模式下充当 eSwitch manager&am…...
物联网技术在银行安全用电系统中的应用与实践研究
摘要 随着金融科技的快速发展,银行业电子设备数量激增,用电安全管理问题日益突出。本文基于2019年农业银行与2020年中国邮政储蓄银行发布的安全用电相关政策,分析了银行场景下存在的五大用电安全隐患,提出以物联网技术为核心的安…...
589. N叉树的前序遍历迭代法:null指针与栈的巧妙配合
一、题目描述 给定一个N叉树的根节点,返回其节点值的前序遍历结果。前序遍历的定义是:先访问根节点,再依次遍历每个子节点(从左到右)。例如,对于如下N叉树: 1/ | \3 2 4 / \ 5 6前序遍历结果…...
【洗车店专用软件】佳易王洗车店多项目会员管理系统:一卡多用扣次软件系统实操教程 #扣次洗车管理软件
一、软件试用版资源文件下载说明 (一)若您想体验软件功能,可通过以下方式获取软件试用版资源文件: 访问头像主页:进入作者头像主页,找到第一篇文章,点击文章最后的卡片按钮,即可了解…...
小红书笔记详情接口如何调用?实操讲解。
调用小红书笔记详情接口通常需要经过申请权限、构建请求、发送请求并处理响应等步骤,以下是详细的实操讲解: 一、申请接口权限 注册小红书开放平台账号 访问小红书开放平台官网/第三方开放平台,按照提示完成注册流程,提供必要的…...
leetcode 57. Insert Interval
题目描述 代码:由于intervals已经按照左端点排序,并且intervals中的区间全部不重叠,那么可以断定intervals中所有区间的右端点也已经是有序的。先二分查找intervals中第一个其右端点>newInterval左端点的区间。然后按照类似于56. Merge In…...
杰理ac696配置mic
省电容mic有概率不出声解决办法如下...
COMSOL随机参数化表面流体流动模拟
基于粗糙度表面的裂隙流研究对于理解地下水的流动、污染物传输以及与之相关的地质灾害(如滑坡)等方面具有重要意义。本研究通过蒙特卡洛方法生成随机表面形貌,并利用COMSOL Multiphysics对随机参数化表面的微尺度流体流动进行模拟。 参数化…...
Linux远程连接服务
远程连接服务器简介 远程连接服务器通过文字或图形接口方式来远程登录系统,让你在远程终端前登录linux主机以取得可操作主机接口(shell),而登录后的操作感觉就像是坐在系统前面一样。 远程连接服务器的功能 分享主机的运算能力 远…...
用Python绘制梦幻星空
用Python绘制梦幻星空 在这篇教程中,我们将学习如何使用Python创建一个美丽的星空场景。我们将使用Python的图形库Pygame和随机库来创建闪烁的星星、流星和月亮,打造一个动态的夜空效果。 项目概述 我们将实现以下功能: 创建深蓝色的夜…...
EWOMAIL
1、错误 Problem: problem with installed package selinux-policy-targeted-3.14.3-41.el8.noarch package fail2ban-server-1.0.2-3.el8.noarch requires (fail2ban-selinux if selinux-policy-targeted), but none of the providers can be installed - package fail2ban-…...
网安面试经(1)
1.说说IPsec VPN 答:IPsec VPN是利用IPsec协议构建的安全虚拟网络。它通过加密技术,在公共网络中创建加密隧道,确保数据传输的保密性、完整性和真实性。常用于企业分支互联和远程办公,能有效防范数据泄露与篡改,但部署…...
【每天一个知识点】意图传播(Intent Propagation)
在人工智能(AI)快速发展的背景下,自然语言处理(NLP)已成为推动智能系统理解与生成自然语言的核心技术。其中,“意图识别”作为人机交互的关键步骤,已被广泛应用于智能客服、对话系统、语音助手等场景。而“意图传播”(Intent Propagation)作为更深层的机制,逐渐成为当…...
【串流VR手势】Pico 4 Ultra Enterprise 在 SteamVR 企业串流中无法识别手势的问题排查与解决过程(Pico4UE串流手势问题)
写在前面的话 此前(用Pico 4U)接入了MRTK3,现项目落地需要部署,发现串流场景中,Pico4UE的企业串流无法正常识别手势。(一体机方式部署使用无问题) 花了半小时解决,怕忘,…...
工具:shell命令提示符自定义之显示GIT当前分支
1 背景 在命令行操作,每次想查看当前分支都要手动执行命令(git branch)太麻烦了,想着在命令提示符上面显示当前分支,很直观也很方便 2 实现 编辑 vim ~/.bashrc 文件,添加如下内容 function update_prom…...
现代计算机图形学Games101入门笔记(十四)
Irradiance 微小的能量/微小的面积 用Irradiance解释能量大小解释冬夏 Intensity没变,但是Irradiance是衰减的,外圈面积变大,单位面积上接受的能量就变小了。 入射进来 离开 这里就是从某个方向来了一个能量,经过反射,…...
前端开发笔记与实践
一、Vue 开发规范与响应式机制 1. 组件命名规范 自定义组件使用大驼峰命名法(如 MyComponent),符合 Vue 官方推荐,便于与原生 HTML 元素区分。 2. Proxy vs defineProperty 特性Proxy(Vue3)Object.defi…...
机器学习知识自然语言处理入门
一、引言:当文字遇上数学 —— 自然语言的数字化革命 在自然语言处理(NLP)的世界里,计算机要理解人类语言,首先需要将文字转化为数学向量。早期的 One-Hot 编码如同给每个词语分配一个唯一的 “房间号”,例…...
泰迪杯特等奖案例深度解析:基于多级二值化与CNN回归的车牌识别系统设计
(第八届泰迪杯数据挖掘挑战赛特等奖案例全流程拆解) 一、案例背景与核心挑战 1.1 行业痛点与场景需求 在智慧交通与无感支付场景中,车牌识别是核心环节。传统车牌识别系统在复杂光照、污损车牌、多角度倾斜等场景下存在显著缺陷。根据某智慧油站2024年运营数据显示,高峰期…...
ai agent(智能体)开发 python高级应用5:crawl4ai 如何建立一个全面的知识库 第一步找分类
让我们充分利用爬虫功能建立自己丰富的知识库, 第一步找分类 以下是一个层次分明、覆盖全面的知识库分类体系,分为9大主类、43个子类,并融入交叉学科和新兴领域设计: 一、经济与商业 宏观经济(全球经济/国家政策&a…...
Solon Ai Flow 编排开发框架发布预告(效果预览)
Solon Ai 在推出 Solon Ai Mcp 后,又将推出 Solon Ai Flow。 1、Solon Ai Flow 是个啥? Solon Ai Flow 是一个智能体编排开发框架。它是框架!不是工具,不是产品(这与市面上流行的工具和产品,有较大差别&a…...
【言语】刷题5(填空)
front:刷题5 第一个词排除人迹罕至 人迹罕至:很少有人去的地方。指偏僻荒涼的地方。(荒郊野岭既视感的一个词) 第二个空锁定B,太贴合语义了 第三个空排除一文不值,百无一用,现在这题已经可以过了…...
技术解码 | 腾讯云SRT弱网优化
随着互联网基础设施和硬件设备的不断发展。广大直播观众对于直播观看的清晰度,延时等方面的体验要求越来越高,直播也随之进入了低延迟高码率的时代,直播传输技术也面临着越来越高的要求和挑战。 腾讯视频云为此在全链路上针对流媒体传输不断深…...
“分布形态“
一、分布形态的基础分类 1、正态分布(对称分布) (1)特征:钟型曲线,均值=中位数=众数;约68%数据在μσ范围内,95%在μ2σ内。 (2)应用:身高、体重、测量误差等自然现象。 (3)重要性:多数统计方法(如T检验、方差分析)假设数据正态性。 2、偏态分布 (1)左偏(负…...
Android minSdk从21升级24后SO库异常
问题 minSdk从21调整到24后: java.nio.file.NoSuchFileException: /data/app/~~Z9s2NfuDdclOUwUBLKnk0A/com.rs.unity- Bg31QvFwF4qsCwv2XCqT-w/split_config.arm64_v8a.apkjava.nio.file.NoSuchFileException: /data/app/~~Z9s2NfuDdclOUwUBLKnk0A/com.rs.unity-…...
C#进阶(2)stack(栈)
前言 我们前面介绍了ArrayList,今天就介绍另一种数据结构——栈。 这是栈的基本形式,博主简单画了一下,你看个意思就行,很明显,这种数据有一种特征:先进后出。因为先进来的数据会在下面,下面是密闭的,所以只能取后面进来的。 C#为我们封好了这种数据结构,我们不用担…...