webpack打包按需加载组件库

前言

之前用组件库的时候,一直不知道原来按需加载的组件库打包是不一样的,单纯的以为只要export {} ,然后import的时候解构获取就可以(获取是可以的,但是打包会全部打进去,没有真正实现按需加载)。但其实像elementui这些组件库,全量组件打包和单个组件打包都是有的。
正好工作中需要封装组件库,学习一下webpack怎么拆分打包组件实现按需加载,至于为什么使用webpack而不是rollup,只是因为webpack平时接触的多,多少会一点。并且记录下遇到的一个巨坑的问题

目标

  1. 全量打包
  2. 组件单个打包
  3. 测试项目中按需加载

准备工作 ------ 先把页面跑起来

1. 初始化项目

npm init

2. 下载需要的依赖包

  • “vue”: “^2.6.14”, // 项目是vue2的
  • “vue-loader”: “^15.10.0” // 解析vue文件
  • “vue-template-compiler”: “2.6”, // 和vue版本一致不然会报错
  • babel-loader // 最新语法转换成es5
  • html-webpack-plugin // 生成html文件,并引入构建好的js文件
  • webpack@5 // 4下载最新sass sass-loader会报错
  • webpack-cli //
  • webpack-dev-server

3. 新建组件包目录文件(代码放末尾1)

	|--- packages
		|--- components
			|--- bar-rate // 某一个组件
				|--- index.js
				|--- index.vue
		|--- style // 全局样式,组件内可以引入,不重要
		|--- hs-ui.js // 所有组件的入口文件

4. 新建示例显示目录文件(代码放末尾2)

	|--- example
		|--- App.vue
		|--- main.js
		|--- index.html
		|--- page
			|--- index.vue // 测试组件效果

5. 配置webpack显示页面

新建webpack.dev.js,我暂时没有用到图片,所以没有加url-loader

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const VueLoaderPlugin = require("vue-loader/lib/plugin");
module.exports = {
  mode: "development",
  entry: {
    index: path.join(__dirname, "../example/", "main.js"),
  },
  devtool: "source-map",
  devServer: {
    open: false,
    hot: true,
    host: "0.0.0.0"
  },
  module: {
    rules: [
      {
        test: /\.(scss|sass|css)$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "babel-loader",
          },
        ],
      },
      {
        test: /\.vue$/,
        use: ["vue-loader"],
      },
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "../example/index.html"),
      filename: "index.html",
    }),
  ],
};

package.json/scripts属性中需要增加命令
"dev": "webpack-dev-server --config ./webpack/webpack.dev.js"
最后执行npm run dev就能在本地把整个项目跑起来,在页面上显示当前书写的组件是否正常显示,后续的打包都是建立在组件代码显示没问题的基础之上的。

组件全量打包

全量打包其实就是将打包配置的入口文件指向全量引用组件的hs-ui.js即可
因为不管是全量打包还是组件打包,都会有一些配置是相同的,所以最开始我是抽出了一些可能会相同的配置属性,但就是这样一个简单的webpack合并配置属性的操作,让我找了两个晚上的bug,走了各种各样的弯路,最后还是将组件打包去掉了merge基础属性
webpack.pro.base.js

const VueLoaderPlugin = require("vue-loader/lib/plugin");
module.exports = {
  mode: "production",
  externals: {
    vue: "vue",
    echarts: "echarts",
  },
  plugins: [new VueLoaderPlugin()],
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.(jsx?|babel|es6)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "babel-loader",
          },
        ],
      },
      {
        test: /\.vue$/,
        use: ["vue-loader"],
      },
    ],
  },
};

webpack.pro.js

const path = require("path");
const BaseConfig = require("./webpack.pro.base");
const { merge } = require("webpack-merge");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = merge(BaseConfig, {
  entry: {
    index: path.join(__dirname, "../packages/", "hs-ui.js"),
  },
  output: {
    filename: "hs-ui.common.js",
    path: path.resolve(__dirname, "../", "lib/"),
    libraryTarget: "umd", // 打成umd的方式
    libraryExport: "default",
  },
  plugins: [new CleanWebpackPlugin()],
});

package.json/scripts属性中增加"build:all": "webpack --config ./webpack/webpack.pro.js"执行npm run build:all即可生成/lib/hs-ui.common.js

组件单独打包

将每一个组件的index.js都作为一个入口文件进行单独打包,而且每一个组件都会生成一个index.js文件和一个index.css文件。是之后项目中按需加载所需要的文件(和element-ui把样式都打个一个主题文件中不太一样)

const path = require("path");
// const BaseConfig = require("./webpack.pro.base");
// const { merge } = require("webpack-merge");
const fs = require("fs");
const miniCssExtractPlugin = require("mini-css-extract-plugin");
const VueLoaderPlugin = require("vue-loader/lib/plugin");
// 获取所有的入口文件
const files = fs.readdirSync(
  path.resolve(__dirname, "../packages/components/")
);
let entry = {};
for (const item of files) {
  const name = item.toLowerCase();
  entry[name] = path.resolve(
    __dirname,
    `../packages/components/${name}/`,
    "index.js"
  );
}
module.exports = {
  mode: "production",
  externals: {
    vue: "vue",
    echarts: "echarts",
  },
  entry,
  output: {
    path: path.resolve(__dirname, "../", "lib"),
    filename: "[name]/index.js",
    libraryTarget: "umd", // 打成umd的方式
    libraryExport: "default",
  },
  module: {
    rules: [
      {
        test: /\.(scss|sass|css)$/,
        use: [
          {
            loader: miniCssExtractPlugin.loader,
          },
          "css-loader",
          "sass-loader",
        ],
      },
      {
        test: /\.(jsx?|babel|es6)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "babel-loader",
          },
        ],
      },
      {
        test: /\.vue$/,
        use: ["vue-loader"],
      },
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
    new miniCssExtractPlugin({
      filename: "[name]/style.css",
    }),
  ],
};

package.json/scripts属性中增加
"build:com": "webpack --config ./webpack/webpack.pro.components.js"
"build": "npm run build:all && npm run build:com"
执行npm run build即可
在这里插入图片描述

项目中按需引入

因为我们当前的组件库并没有上传到npm官网上,所以在另一个项目中直接install是不行的。npm给我们提供了另一种测试的方式:现在当前组件库终端执行npm link,再在需要使用组件库的项目vscode窗口终端中执行npm link hs-ui即npm link 组件库名(package.json里边name属性的值),这样,在项目的node_modules中就会有一个包指向我们本地的组件库文件
在这里插入图片描述
在项目中按需引入组件库,我们要借助一个插件babel-plugin-component,新建babel.config.js

module.exports = {
 // ......其他原有配置 // 
  plugins: [
    [
      'component',
      {
        libraryName: 'hs-ui',
        // libDir: 'lib/packages',
        camel2Dash: true,
      },
    ],
  ],
}

main.js中增加

import { BarRate } from 'hs-ui'
Vue.use(BarRate)

以上代码插件会自动给我们变成引入对应组件的index.js和index.css,然后在项目中就可以使用了。当然测试没问题之后,就可以将组件库上传至npm官网,提供给其他项目使用。

总结

开发的时候遇到一个特别坑的问题,就是在单独打包组件的配置文件中,使用了webpack的merge,相当于在base中写了一个loader,merge之后又写了一个不一样的loader,我以为后面写的loader会覆盖通用的loader。但是它没有,打包也不报错,就是死活打不出css文件,js文件在引用时也有问题,各种报错。查找百度也是找不出具体的问题,一次机缘巧合之前,删除了merge,瞬间,整个世界的开朗了。还是自己对于基础配置的使用不熟悉导致的奇奇怪怪的问题。

代码

1. 组件包

/packages/components/bar-rate/index.vue

<template>
  <div w-full border-box>
    <div class="top-text">
      <span>测试</span>
    </div>
  </div>
</template>
<script>
export default {
  name: "hs-bar-rate"  // 这个名字要定义好,之后项目中就是用的这个组件名,最好有统一的前缀
};
</script>
<style lang="scss" scoped>
@import "../../style/index.scss"; // 公用样式,不需要关注,也可以没有
.top-text {
  width: 100%;
  font-size: 16px;
}
</style>

/packages/components/bar-rate/index.js

import BarRate from "./index.vue";
BarRate.install = (Vue) => {
  Vue.component(BarRate.name, BarRate);
};
export default BarRate;

/packages/hs-ui.js

const requireComponent = require.context(
  // 其组件目录的相对路径
  "./",
  // 是否查询其子目录
  true,
  // 匹配基础组件文件名的正则表达式
  /index.js$/
);
let componentList = [];
requireComponent.keys().forEach((fileName) => {
  // 获取组件配置
  let componentConfig = requireComponent(fileName);
  // 如果这个组件选项是通过 `export default` 导出的,
  // 那么就会优先使用 `.default`,
  // 否则回退到使用模块的根。
  componentList.push(componentConfig.default || componentConfig);
});
const install = (Vue) => {
  // 判断是否安装过
  if (install.installed) return;
  // 注册所有组件
  componentList.map((component) => {
    Vue.use(component);
  });
};
export default {
  install
  // 像elementui在这里又抛出了所有组件包,不知道是为什么
};

2. 示例代码(框架内页面测试)

example/index.html

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8" />
    <meta content="IE=edge" http-equiv="X-UA-Compatible" />
    <meta content="width=device-width,initial-scale=1.0" name="viewport" />
    <!-- <title><%= htmlWebpackPlugin.options.title %></title> -->
    <style>
      html, body{
        margin:0;
        height: 100%;
      }
    </style>
  </head>
  <body>
    <noscript>
      <strong
        >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
        properly without JavaScript enabled. Please enable it to
        continue.</strong
      >
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

example/App.vue

<template>
  <DemoPage />
</template>
<script>
import DemoPage from "./page/index.vue";
export default {
  name: "App",
  components: { DemoPage },
};
</script>

example/page/index.vue

<template>
  <div class="page">
    <div class="box">
      <hs-bar-rate />
    </div>
  </div>
</template>
<script>
export default {
  name: "demo-page",
};
</script>
<style lang="scss" scoped>
.page {
  height: 100%;
  width: 100%;
  background: #02111c;
  display: flex;
  .box {
    width: 25%;
    height: 200px;
    border: 1px solid blue;
  }
}
</style>

example/main.js

import Vue from "vue";
import App from "./App.vue";
// import hsui from "../packages/hs-ui";  // 测试未打包时的功能
// import hsui from "../lib/hs-ui.common"; // 测试打包完之后的全量包功能
// Vue.use(hsui);
// import BarRate from "../packages/components/bar-rate";  // 测试打包前,单个包引入的功能
import BarRate from "../lib/bar-rate/index"; // 测试组件分别打包生成文件功能
import "../lib/bar-rate/style.css";
Vue.use(BarRate);
new Vue({
  render: (h) => h(App),
}).$mount("#app");
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值