elementUI源码学习

news2025/5/17 16:10:15

学习笔记。

最近在看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 =》 FormItem
  var 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";\n
  var indexContent = isSCSS ? '@import "./base.scss";\n' : '@import "./base.css";\n';
  // 遍历所有组件包,并生成 @import "./comp-package.scss|css";\n
  Components.forEach(function(key) {
    // 跳过这三个组件包
    if (['icon', 'option', 'option-group'].indexOf(key) > -1) return;
    // comp-package.scss|css
    var fileName = key + (isSCSS ? '.scss' : '.css');
    // 导入语句,@import "./comp-package.scss|css";\n
    indexContent += '@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-CN
  try {
    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.tpl
    var templatePath = path.resolve(__dirname, `../../examples/pages/template/${ page }.tpl`);
    // /examples/pages/zh-CN/index.vue
    var outputPath = path.resolve(__dirname, `../../examples/pages/${ lang.lang }/${ page }.vue`);
    // 读取模版文件
    var content = fs.readFileSync(templatePath, 'utf8');
    // 读取 index 页面的所有键值对的配置
    var pairs = lang.pages[page];

    // 遍历这些键值对,通过正则匹配的方式替换掉模版中对应的 key
    Object.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,匹配得到 add
  var 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',
    // 文件内容,sfc
    content: `<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/;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2377800.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SZU 编译原理

总结自 深圳大学《编译原理》课程所学相关知识。 文章目录 文法语法分析自顶向下的语法分析递归下降分析LL(1) 预测分析法FIRST 集合FOLLOW 集合 文法 乔姆斯基形式语言理论&#xff1a; 表达能力&#xff1a;0型文法 > 1型文法 > 2型文法 > 3型文法。 0 型文法&am…

【程序员AI入门:模型】19.开源模型工程化全攻略:从选型部署到高效集成,LangChain与One-API双剑合璧

一、模型选型与验证&#xff1a;精准匹配业务需求 &#xff08;一&#xff09;多维度评估体系 通过量化指标权重实现科学选型&#xff0c;示例代码计算模型综合得分&#xff1a; # 评估指标权重与模型得分 requirements {"accuracy": 0.4, "latency": …

ARM Cortex-M3内核详解

目录 一、ARM Cortex-M3内核基本介绍 &#xff08;一&#xff09;基本介绍 &#xff08;二&#xff09;主要组成部分 &#xff08;三&#xff09;调试系统 二、ARM Cortex-M3内核的内核架构 三、ARM Cortex-M3内核的寄存器 四、ARM Cortex-M3内核的存储结构 五、ARM Co…

ThinkStation图形工作站进入BIOS方法

首先视频线需要接在独立显卡上&#xff0c;重新开机&#xff0c;持续按F1&#xff0c;或者显示器出来lenovo的logo的时候按F1&#xff0c;这样就进到bios里了。联*想*坑&#xff0c;戴尔贵。靠。

go 集成base64Captcha 支持多种验证码

base64Captcha 是一个基于 Go 语言开发的验证码生成库&#xff0c;主要用于在 Web 应用中集成验证码功能&#xff0c;以增强系统的安全性。以下是其主要特点和简介&#xff1a; base64Captcha主要功能 验证码类型丰富&#xff1a;支持生成多种类型的验证码&#xff0c;包括纯…

【C语言字符函数和字符串函数(一)】--字符分类函数,字符转换函数,strlen,strcpy,strcat函数的使用和模拟实现

目录 一.字符分类函数 1.1--字符分类函数的理解 1.2--字符分类函数的使用 二.字符转换函数 2.1--字符转换函数的理解 2.2--字符转换函数的使用 三.strlen的使用和模拟实现 3.1--strlen的使用演示 3.2--strlen的返回值 3.3--strlen的模拟实现 四.strcpy的使用和模拟实现…

大模型基础之量化

概述 量化&#xff0c;Quantization&#xff0c;机器学习和深度学习领域是一种用于降低计算复杂度、减少内存占用、加速推理的优化方法。定义&#xff1a;将模型中的数据从高精度表示转换为低精度表示。主要目的是为了减少模型的存储需求和计算复杂度&#xff0c;同时尽量减少…

游戏引擎学习第286天:开始解耦实体行为

回顾并为今天的内容定下基调 我们目前正在进入实体系统的一个新阶段&#xff0c;之前我们已经让实体的移动系统变得更加灵活&#xff0c;现在我们想把这个思路继续延伸到实体系统的更深层次。今天的重点&#xff0c;是重新审视我们处理实体类型&#xff08;entity type&#x…

win10-django项目与mysql的基本增删改查

以下都是在win10系统下&#xff0c;django项目的orm框架对本地mysql的表的操作 models.py----->即表对应的类所在的位置 在表里新增数据 1.引入表对应的在models.py中的类class 2.在views.py中使用函数&#xff1a;类名.objects.create(字段名值,字段名"值"。。。…

动态范围调整(SEF算法实现)

一、背景介绍 继续在整理对比度调整相关算法&#xff0c;发现一篇单帧动态范围提升的算法&#xff1a;Simulated Exposure Fusion&#xff0c;论文表现看起来很秀&#xff0c;这里尝试对它进行了下效果复现。 二、实现流程 1、基本原理 整体来说&#xff0c;大致可以分为两步…

SpringCloud微服务开发与实战

本节内容带你认识什么是微服务的特点&#xff0c;微服务的拆分&#xff0c;会使用Nacos实现服务治理&#xff0c;会使用OpenFeign实现远程调用&#xff08;通过黑马商城来带你了解实际开发中微服务项目&#xff09; 前言&#xff1a;从谷歌搜索指数来看&#xff0c;国内从自201…

WAS和Tomcat的对比

一、WAS和Tomcat的对比 WebSphere Application Server (WAS) 和 Apache Tomcat 是两款常用的 Java 应用服务器&#xff0c;但它们有许多显著的区别。在企业级应用中&#xff0c;它们扮演不同的角色&#xff0c;各自有其特点和适用场景。以下是它们在多个维度上的详细对比&…

IntelliJ IDEA打开项目后,目录和文件都不显示,只显示pom.xml,怎样可以再显示出来?

检查.idea文件夹 如果项目目录中缺少.idea文件夹&#xff0c;可能导致项目结构无法正确加载。可以尝试删除项目根目录下的.idea文件夹&#xff0c;然后重新打开项目&#xff0c;IDEA会自动生成新的.idea文件夹和相关配置文件&#xff0c;从而恢复项目结构。 问题解决&#xff0…

Hot100-链表-JS

160.相交链表 160. 相交链表 已解答 简单 相关标签 相关企业 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整…

事件驱动架构:从传统服务到实时响应的IT新风潮

文章目录 事件驱动架构的本质&#xff1a;从请求到事件的范式转变在EDA中&#xff1a; 事件驱动架构的演进&#xff1a;从消息队列到云原生标配核心技术&#xff1a;事件驱动架构的基石与工具链1. 消息队列&#xff1a;事件传递的枢纽2. 消费者&#xff1a;异步处理3. 事件总线…

网络流量分析 | NetworkMiner

介绍 NetworkMiner 是一款适用于Windows&#xff08;也适用于Linux/Mac&#xff09;的开源网络取证分析工具。它可被用作被动网络嗅探器/数据包捕获工具&#xff0c;也可被用于检测操作系统、会话、主机名、开放端口等&#xff0c;还能被用于解析pcap文件进行离线分析。点击此…

深入理解 Git 分支操作的底层原理

在软件开发的世界里&#xff0c;Git 已经成为了版本控制的标配工具。而 Git 分支功能&#xff0c;更是极大地提升了团队协作和项目开发的效率。我们在日常开发中频繁地创建、切换和合并分支&#xff0c;但是这些操作背后的底层原理是怎样的呢&#xff1f;在之前的博客探秘Git底…

Excel MCP: 自动读取、提炼、分析Excel数据并生成可视化图表和分析报告

最近&#xff0c;一款Excel MCP Server的开源工具火了&#xff0c;看起来功能很强大&#xff0c;咱们今天来一探究竟。 基础环境 最近两年&#xff0c;大家都可以看到AI的发展有多快&#xff0c;我国超10亿参数的大模型&#xff0c;在短短一年之内&#xff0c;已经超过了100个&…

C语言:深入理解指针(4)

目录 一、字符指针变量 二、数组指针变量 三、二维数组传参的本质 四、函数指针变量 五、typedef 类型重命名 六、函数指针数组 一、字符指针变量 我们常见的字符指针变量是这样的&#xff1a; char a w; char* p &a; char arr[] "abcd"; char* pa ar…

【更新】全国省市县-公开手机基站数据集(2006-2025.3)

手机基站是现代通信网络中的重要组成部分&#xff0c;它们为广泛的通信服务提供基础设施。随着数字化进程的不断推进&#xff0c;手机基站的建设与布局对优化网络质量和提升通信服务水平起着至关重要的作用&#xff0c;本分享数据可帮助分析移动通信网络的发展和优化。本次数据…