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 =》 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/;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值