elementUI

2023.12 ElementUI源码-组件打包源码分析

2023-03-01  本文已影响0人  wo不是黄蓉

大家好,我是wo不是黄蓉,今年学习目标从源码共读开始,希望能跟着若川大佬学习源码的思路学到更多的东西。

最近在写组件库的时候,在使用elementui的基础上进行了封装,但是,我想按需引入组件,然后将引入的组件放在一个数组里面,循环进行注册,结果就提示找不到样式系统。试了不使用as然后循环注册组件就不会报错,好吧,不知道咋回事,然后就想看下element里面怎么引入样式系统和打包.

感觉不去做永远不会踩坑,然后就永远不会学到新东西,工作中稍微加点挑战挺开心的,哈哈

image.png

编译时候就报错

image.png

使用全局引入组件

import Vue from 'vue';
import ElementUI from 'element-ui';
import App from './App.vue';

Vue.use(ElementUI);

new Vue({
  el: '#app',
  render: h => h(App)
});

使用局部引入组件

import Vue from 'vue';
import App from './App.vue';
import { Button } from 'element-ui'

Vue.use(Button)
new Vue({
  el: '#app',
  render: h => h(App)
});

组件打包源码分析入口

看组件如何打包的从package.json入手

打包组件主要在这边"dist": "npm run clean && npm run build:file && npm run lint && webpack --config build/webpack.conf.js && webpack --config build/webpack.common.js && webpack --config build/webpack.component.js && npm run build:utils && npm run build:umd && npm run build:theme",

组件打包是怎么实现的?

其实就是根据build-entry.js生成一个入口文件,根据入口文件打包成不同的规范的包(umdcommonjs两种),打包好的内容又分为全部的和单个的,全部的就是按照全局引入的方式使用的(方式一),单个的就是按照按需引入的方式(方式二)

npm run build:file其中最主要的一条命令node build/bin/build-entry.js 使用json-templater/string生成入口文件

目标文件位置为:D:\workspace\element\src\index.js

这点从build\webpack.conf.js的配置中可以看出来


module.exports = {
   entry: {
    app: ["./src/index.js"],
  },
  output: {
    path: path.resolve(process.cwd(), "./lib"),
    publicPath: "/dist/",
    filename: "index.js",
    chunkFilename: "[id].js",
    libraryTarget: "umd",
    libraryExport: "default",
    library: "ELEMENT",
    umdNamedDefine: true,
    globalObject: "typeof self !== 'undefined' ? self : this",
  },
}

源码实现全局引入:通过向外暴露一个install方法

导出单个组件和install方法,单个引入的时候用的Vue.useVue.use就是需要一个install方法,每个组件中都有install方法,全局也有一个install方法,下面的install指的是全局的

/* Automatically generated by './build/bin/build-entry.js' */

import Pagination from '../packages/pagination/index.js';
import Loading from '../packages/loading/index.js';
//省略一些组件的引入

const components = [
  Pagination,
];

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;

};

/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue);
}


export default {
  version: '2.15.12',
  locale: locale.use,
  i18n: locale.i18n,
  install,
  CollapseTransition,
  Loading,
  Pagination
};

组件里的install,为了方便单个组件的引入

packages\alert\index.js

import Alert from './src/main';

/* istanbul ignore next */
Alert.install = function(Vue) {
  Vue.component(Alert.name, Alert);
};

export default Alert;

打包成commonjs

通过 build/webpack.common.jssrc/index.js 转换为 lib/element-ui.common.js

为啥要生成element-ui.common.js呢?

可以通过require方式引入

image.png image.png

什么场景下会用到用require方式引入呢?

又看了一遍搜了下其他答案,还是没太知道

打包成commonjswebpack配置:


  entry: {
    app: ['./src/index.js']
  },
  output: {
    path: path.resolve(process.cwd(), './lib'),
    publicPath: '/dist/',
    filename: 'element-ui.common.js',
    chunkFilename: '[id].js',
    libraryExport: 'default',
    library: 'ELEMENT',
    libraryTarget: 'commonjs2'
  },

package.json 配置入口 "main": "lib/element-ui.common.js" 用于项目全局引入 import ElementUI from 'element-ui'

单个组件的引入方式

webpack --config build/webpack.component.js
入口文件为每个文件,打包成单个的js

image.png

这边components中的内容也相当于是一个入口文件,注册方式前面讲过,参考前面的内容

{
  "alert": "./packages/alert/index.js",
}

样式是怎么打包的?

node build/bin/gen-cssfile

其中打包里面有一句这样的编译命令:npm run build:theme即为样式打包的实现

node build/bin/gen-cssfile根据packages\theme-chalk下的文件生成css文件

var fs = require('fs');
var path = require('path');
var Components = require('../../components.json');
var themes = [
  'theme-chalk'
];
Components = Object.keys(Components);
var basepath = path.resolve(__dirname, '../../packages/');

function fileExists(filePath) {
  try {
    return fs.statSync(filePath).isFile();
  } catch (err) {
    return false;
  }
}

themes.forEach((theme) => {
  var isSCSS = theme !== 'theme-default';
  var indexContent = isSCSS ? '@import "./base.scss";\n' : '@import "./base.css";\n';
  Components.forEach(function(key) {
    if (['icon', 'option', 'option-group'].indexOf(key) > -1) return;
    var fileName = key + (isSCSS ? '.scss' : '.css');
    indexContent += '@import "./' + fileName + '";\n';
    var filePath = path.resolve(basepath, theme, 'src', fileName);
    if (!fileExists(filePath)) {
      fs.writeFileSync(filePath, '', 'utf8');
      console.log(theme, ' 创建遗漏的 ', fileName, ' 文件');
    }
  });
  fs.writeFileSync(path.resolve(basepath, theme, 'src', isSCSS ? 'index.scss' : 'index.css'), indexContent);
});

gulp build --gulpfile packages/theme-chalk/gulpfile.js

gulp src创建一个流,用于从文件系统读取vinyl(虚拟文件对象)

gulp series将任务和/或组合操作组合成更大的操作,这些操作将按顺序依次执行

const { series, src, dest } = require('gulp');
const sass = require('gulp-sass');
const autoprefixer = require('gulp-autoprefixer');
const cssmin = require('gulp-cssmin');

function compile() {
    //匹配src下所有的.scss文件;
    //pipe把流导入到你想导入的地方;
    //sass.sync把sass转换成css;
    //autoprefixer转换后的css自动补全css浏览器前缀;
    //cssmin压缩css代码
    //dest把转好的css文件存入css文件夹
  return src('./src/*.scss')
    .pipe(sass.sync())
    .pipe(autoprefixer({
      browsers: ['ie > 9', 'last 2 versions'],
      cascade: false
    }))
    .pipe(cssmin())
    .pipe(dest('./lib'));
}

function copyfont() {
  return src('./src/fonts/**')
    .pipe(cssmin())
    .pipe(dest('./lib/fonts'));
}

//按顺序执行这compile和copyfont函数
exports.build = series(compile, copyfont);

cp-cli packages/theme-chalk/lib lib/theme-chalk

package.json cp-cli packages/theme-chalk/lib lib/theme-chalk

cp-cli文件复制工具,将packages/theme-chalk/lib 移动到 lib/theme-chalk,用于单独的样式引入

总结

element-ui组件可以按照按需引入的方式进行引入,通过webpack --config build/webpack.conf.js && webpack --config build/webpack.common.js打包的是全部组件的包,分别是umdcommonjs格式

打包成局部组件的包:通过webpack --config build/webpack.component.js实现的

css的按需引入通过单独引入某个文件实现

//全局
import 'element-ui/lib/theme-chalk/index.css'
//局部
import 'element-ui/lib/theme-chalk/button.css'

遗留问题,为什么使用as就引入不到组件的问题没解决

我的问题,既然样式系统和组件按需导入没有关系,为什么给组件起了别名之后,就引用不到对应的样式?

这个问题这边应该是找到了答案,参考链接

就是按需引入的组件最终会被转换成这种形式,前提是使用了[babel](https://so.csdn.net/so/search?q=babel&spm=1001.2101.3001.7020)-plugin-component这个webpack插件

配置信息

  "plugins": [
    "transform-vue-jsx",
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": 3
      }
    ],
    [
      "component",
      {
        "libraryName": "@zto/zui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ],

按需引入最后会被转换成以下格式:

image.png

所以会自动引入button.css文件,由于我们给组件做了别名,所以查找组件的时候会按照引入z-button.css来加载,但是此时lib目录下的theme-chalk没有z-button.css文件因此就会报错

打包好的文件的目录结构

image.png
上一篇下一篇

猜你喜欢

热点阅读