- 本阶段围绕当下国内最主流的前端核心框架 Vue.js 展开,深入框架内部,通过解读源码或者手写实现的方式,剖析 Vue.js 框架的内部实现原理,让你做到知其所以然。同时我们还会介绍 Vue.js 的进阶用法、周边生态以及性能优化,让你轻松应对更加复杂的项目业务需求。
模块四 搭建自己的SSR、静态站点生成(SSG)及封装 Vue.js 组件库
- 本模块会带你深入 SSR 的实现,自己动手搭建一个自己的基于 Vue.js 的 SSR,然后使用静态站点生成器快速生成静态网站。最后会教会你如何开发自己的基础/业务组件,一起来 Happy 的造轮子,不做只会用别人组件的码农。
任务一:搭建自己的SSR
- 渲染一个Vue实例
- mkdri vue-ssr
- cd vue-ssr
- npm init -y
- npm i vue vue-server-renderer
- server.js
- 结合到Web服务中
- npm i express cross-env
- 使用HTML模板
- index.template.html
- 在模板中使用外部数据
- {{{}}} 模板中三个大括号,原文输出(html)
- 构建配置-基本思路
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oFEANm12-1627836997520)(./img/1627176435770.jpg)]
- https://ssr.vuejs.org/zh/guide/structure.html#%E4%BD%BF%E7%94%A8-webpack-%E7%9A%84%E6%BA%90%E7%A0%81%E7%BB%93%E6%9E%84
- 构建配置-源码结构
src
├── components
│ ├── Foo.vue
│ ├── Bar.vue
│ └── Baz.vue
├── App.vue
├── app.js # 通用 entry(universal entry)
├── entry-client.js # 仅运行于浏览器
└── entry-server.js # 仅运行于服务器
<!-- App.vue -->
<template>
<!-- 客户端渲染的入口节点 -->
<div id="app">
<h1>拉勾教育</h1>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
</style>
// app.js
import Vue from 'vue'
import App from './App.vue'
// 导出一个工厂函数,用于创建新的
// 应用程序、router 和 store 实例
export function createApp () {
const app = new Vue({
// 根实例简单的渲染应用程序组件。
render: h => h(App)
})
return { app }
}
- 客户端 entry 只需创建应用程序,并且将其挂载到 DOM 中:
import { createApp } from './app'
// 客户端特定引导逻辑……
const { app } = createApp()
// 这里假定 App.vue 模板中根元素具有 `id="app"`
app.$mount('#app')
- entry-server.js
- 服务器 entry 使用 default export 导出函数,并在每次渲染中重复调用此函数。此时,除了创建和返回应用程序实例之外,它不会做太多事情 - 但是稍后我们将在此执行服务器端路由匹配 (server-sideroute matching) 和数据预取逻辑 (data pre-fetching logic)。
import { createApp } from './app'
export default context => {
const { app } = createApp()
return app
}
- server.js
/**
* 通用应用 Web 服务启动脚本
*/
const express = require('express')
const Vue = require('vue')
const VueServerRenderer = require('vue-server-renderer')
const fs = require('fs')
// 创建一个 express 实例
const server = express()
// 生成一个渲染器
const renderer = VueServerRenderer.createRenderer({
// 渲染器就会自动把渲染的结果注入到模板中
template: fs.readFileSync('./index.html', 'utf-8')
})
const createApp = () => {
const app = new Vue({
template: `
<div id="app">
<h1>Hello {{ message }}</h1>
<input v-model="message">
</div>
`,
data: {
message: 'World'
}
})
return app
}
server.get('/foo', (req, res) => {
const app = createApp()
app.message = '世界'
res.end('foo')
})
// 设置一个路由
server.get('/', async (req, res) => {
// const app = new Vue({
// template: `
// <div id="app">
// <h1>Hello {{ message }}</h1>
// <input v-model="message">
// </div>
// `,
// data: {
// message: 'World'
// }
// })
try {
const app = createApp()
const ret = await renderer.renderToString(app, {
title: '自定义页面标题',
meta: `
<meta name="description" content="hello world">
`
})
res.end(ret)
} catch (err) {
res.status(500).end('Internal Server Error.')
}
})
// 监听端口,启动 Web 服务
server.listen(3000, () => {
console.log('running at port 3000.')
})
- index.template.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>拉勾教育</title>
</head>
<body>
<!-- 服务端渲染的内容出口 -->
<!--vue-ssr-outlet-->
</body>
</html>
- 构建配置-安装依赖
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PUfg8dhh-1627836997522)(./img/1627177187776.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZRaiNoee-1627836997524)(./img/1627177140623.jpg)]
npm i -D webpack webpack-cli webpack-merge webpack-node-externals @babel/core @babel/plugin-transform-runtime @babel/preset-env babel-loader css-loader url-loader file-loader rimraf vue-loader vue-template-compiler friendly-errors-webpack-plugin
- 构建配置-webpack配置文件
- 初始化 webpack 打包配置文件
build
├── webpack.base.config.js # 公共配置
├── webpack.client.config.js # 客户端打包配置文件
└── webpack.server.config.js # 服务端打包配置文件
- webpack.base.config.js
/**
* 公共配置
*/
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const path = require('path')
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
const resolve = file => path.resolve(__dirname, file)
const isProd = process.env.NODE_ENV === 'production'
module.exports = {
mode: isProd ? 'production' : 'development',
output: {
path: resolve('../dist/'),
publicPath: '/dist/',
filename: '[name].[chunkhash].js'
},
resolve: {
alias: {
// 路径别名,@ 指向 src
'@': resolve('../src/')
},
// 可以省略的扩展名
// 当省略扩展名的时候,按照从前往后的顺序依次解析
extensions: ['.js', '.vue', '.json']
},
devtool: isProd ? 'source-map' : 'cheap-module-eval-source-map',
module: {
rules: [
// 处理图片资源
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
},
},
],
},
// 处理字体资源
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader',
],
},
// 处理 .vue 资源
{
test: /\.vue$/,
loader: 'vue-loader'
},
// 处理 CSS 资源
// 它会应用到普通的 `.css` 文件
// 以及 `.vue` 文件中的 `<style>` 块
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
},
// CSS 预处理器,参考:https://vue-loader.vuejs.org/zh/guide/preprocessors.html
// 例如处理 Less 资源
// {
// test: /\.less$/,
// use: [
// 'vue-style-loader',
// 'css-loader',
// 'less-loader'
// ]
// },
]
},
plugins: [
new VueLoaderPlugin(),
new FriendlyErrorsWebpackPlugin()
]
}
- webpack.client.config.js
/**
* 客户端打包配置
*/
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.base.config.js')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
module.exports = merge(baseConfig, {
entry: {
app: './src/entry-client.js' // 相对的是打包时候运行的路径
},
module: {
rules: [
// ES6 转 ES5
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
cacheDirectory: true,
plugins: ['@babel/plugin-transform-runtime']
}
}
},
]
},
// 重要信息:这将 webpack 运行时分离到一个引导 chunk 中,
// 以便可以在之后正确注入异步 chunk。
optimization: {
splitChunks: {
name: "manifest",
minChunks: Infinity
}
},
plugins: [
// 此插件在输出目录中生成 `vue-ssr-client-manifest.json`。
new VueSSRClientPlugin()
]
})
- webpack.server.config.js
/**
* 服务端打包配置
*/
const { merge } = require('webpack-merge')
const nodeExternals = require('webpack-node-externals')
const baseConfig = require('./webpack.base.config.js')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
module.exports = merge(baseConfig, {
// 将 entry 指向应用程序的 server entry 文件
entry: './src/entry-server.js',
// 这允许 webpack 以 Node 适用方式处理模块加载
// 并且还会在编译 Vue 组件时,
// 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。
target: 'node',
output: {
filename: 'server-bundle.js',
// 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports)
libraryTarget: 'commonjs2'
},
// 不打包 node_modules 第三方包,而是保留 require 方式直接加载
externals: [nodeExternals({
// 白名单中的资源依然正常打包
allowlist: [/\.css$/]
})],
plugins: [
// 这是将服务器的整个输出构建为单个 JSON 文件的插件。
// 默认文件名为 `vue-ssr-server-bundle.json`
new VueSSRServerPlugin()
]
})
- 构建配置-配置构建命令
"scripts": {
"build:client": "cross-env NODE_ENV=production webpack --config build/webpack.client.config.js",
"build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.config.js",
"build": "rimraf dist && npm run build:client && npm run build:server"
},
-
构建配置-启动应用
-
构建配置-解析渲染流程
- 服务端渲染
- renderer 是如何拿到 entry-server 模块的?
- createBundleRenderer 中的 serverBundle
- server Bundle 是 Vue SSR 构建的一个特殊的 JSON 文件
- entry:入口
- files:所有构建结果资源列表
- maps:源代码 source map 信息
- server-bundle.js 就是通过 server.entry.js 构建出来的结果文件
- 最终把渲染结果注入到模板中
- renderer 是如何拿到 entry-server 模块的?
- 客户端渲染
- vue-ssr-client-manifest.json
- publicPath:访问静态资源的根相对路径,与 webpack 配置中的 publicPath 一致
- all:打包后的所有静态资源文件路径
- initial:页面初始化时需要加载的文件,会在页面加载时配置到 preload 中
- async:页面跳转时需要加载的文件,会在页面加载时配置到 prefetch 中
- modules:项目的各个模块包含的文件的序号,对应 all 中文件的顺序;moduleIdentifier和和all数组中文件的映射关系(modules对象是我们查找文件引用的重要数据)
- vue-ssr-client-manifest.json
- 构建配置开发模式-基本思路
- 生产模式
- npm run build 构建
- node server.js 启动应用
- 开发模式
- 监视代码变动自动构建,热更新等功能
- node server.js 启动应用
"scripts": {
// ...
// 启动开发服务
"dev": "node server.js",
// 启动生产服务
"start": "cross-env NODE_ENV=production && node server.js"
}
-
构建配置开发模式-提取处理模块
-
构建配置开发模式-update更新函数
-
构建配置开发模式-处理模板文件
-
构建配置开发模式-服务端监视打包
-
构建配置开发模式-把数据写入内存中
- npm install webpack-dev-middleware --save-dev 把打包的结果放入内存
-
构建配置开发模式-客户端构建
-
构建配置开发模式-热更新
-
编写通用应用注意事项
-
路由处理-配置VueRouter
-
路由处理-将路由注册到根实例
-
路由处理-适配服务端入口
-
路由处理-服务端server适配
-
路由处理-适配客户端入口
-
路由处理-处理完成
-
管理页面Head内容
- vue-meta
-
数据预取和状态管理-思路分析
-
数据预取和状态管理-数据预取
- npm i vuex
- 数据预取和状态管理-将预取数据同步到客户端
任务二:静态站点生成
-
关于本任务中gridsome安装包下载不下来解决方法
-
Gridsome基础-介绍
- 一个免费、开源、基于Vue.js技术栈的静态网站生成器
- 官方网站:https://gridsome.org/
- 什么是静态网站生成器
- 静态网站生成器是使用一系列配置、模版以及数据,生成静态HTML文件及相关资源的工具
- 这个功能也叫预渲染
- 生成的网站不需要类似PHP这样的服务器
- 只想要放到支持静态资源的 Web Server 或 DCN 上即可运行
- 静态网站的好处
- 省钱:不需要专业的服务器,只要能托管静态文件的空间即可
- 快速:不经过后端服务器的处理,只传输内容
- 安全:没有后端程序的执行,自然会更安全
- 常见的静态网站生成器
- Jekyll(Ruby)
- Hexo(Node)
- Hugo(Golang)
- Gatsby(Node/React)
- Gridsome(Node/Vue)
- 另外,Next.js,Nuxt.js 也能生成静态网站,但是它们更多被认为是SSR(服务端渲染)框架。
- JAMStack
- 这类静态网站生成器还有个漂亮的名字叫JAMStack
- JAMStack的JAM是JavaScript、API和Markup的首字母组合
- 本质上是一种胖前端,通过调用各种API来实现更多的功能
- 其实也是一种前后端的模式,只不过离得比较开,甚至前后端来自不同的厂商
- 静态应用的使用场景
- 不适合有大量路由页面的应用
- 网站页面多,预渲染将非常缓慢
- 不合适有大量动态内容的应用
- 不适合有大量路由页面的应用
- Gridsome基础-创建Gridsome项目
- Gridsome 依赖 sharp (处理图片,图片的大小,格式转换)
- 1.https://sharp.pixelplumbing.com/install#chinese-mirror 修改镜像方便安装
- 2.node-gyp Gridsome依赖c++环境编译 https://github.com/nodejs/node-gyp
- 3.强行打断安装,然后删除安装来一半的mode-modules ,npm i 重新安装
- Gridsome基础-预渲染
- npm i -g serve 基于node开发的静态web服务
- serve dist 跟上项目的静态文件目录
- 客户端交互都是单页面应用,除非强制刷新页面
-
Gridsome基础-目录结构
http; 目录结构
-
Gridsome基础-Pages
-
Gridsome基础-添加集合
- http; 提供数据的网站
- Gridsome基础-添加集合
- Importing data
// gridsome.server.js
const axios = require('axios')
module.exports = function (api) {
api.loadSource(async actions => {
const { posts } = await axios.get('https://api.example.com/posts')
const collection = actions.addCollection({
typeName: 'BlogPosts'
})
for (const post of posts) {
collection.addNode({
id: post.id,
title: post.title
})
}
})
}
- Gridsome基础-在GraphQL中查询数据
- 查看数据 http://localhost:8080/___explore
query {
blogPosts(id: 1) {
id,
title
}
}
- Gridsome基础-在页面中查询GraphQL
- Collections > query nodes in GraphQL > Querying data
- serve dist/ 启动静态文件服务器查看
- Gridsome基础-使用模板渲染节点页面
- Templates
- 可以批量生成列表也对应的详情页
- Gridsome案例-创建项目
- gridsome create blog-with-gridsome
- Gridsome案例-处理首页模板
- http; 模版地址
- fork 到自己的仓库,防止开源库作者删除项目
- git clone 地址 --depth=1 只下载最后一次提交的历史
- cd 到一个项目中 code -a . 两个同一目录的项目打开到同一个编辑器中
- blog-with-gridsome
- main.js 加载css
- npm i bootstrap @fortawesome/fontawesome-free
- import ‘bootstrap/dist/css/bootstrap.min.css’ import ‘@fortawesome/fontawesome-free/css/all.min.css’
- src>assets/css/index.css @import url(“字体图标地址”)
- Gridsome案例-处理首页模板
- layout留一个插槽
-
Gridsome案例-使用本地md文件管理文章内容
-
Gridsome案例-Strapi介绍
- http; strapi官网
- 可以搭建一个后台管理系统
-
Gridsome案例-Strapi基本使用
-
Gridsome案例-使用Strapi接口数据
-
Gridsome案例-访问受保护的API
-
Gridsome案例-通过GraphQL访问Strapi
- yarn strapi install graphql
- Strapi plugins > GraphQL 可以在这里通过GraphQL访问Strapi
- Gridsome案例-将Strapi数据预取到Gridsome应用中
- 在 gridsome 官网中plugins中搜索 strapi
- 通过插件加载 strapi 中的数据
-
Gridsome案例-设计文章和标签数据模型
-
Gridsome案例-展示文章列表
-
Gridsome案例-文章列表分页
- Paginate data
-
Gridsome案例-展示文章详情
-
Gridsome案例-处理Markdown格式的文章内容
- markdown-it
-
Gridsome案例-文章标签
-
Gridsome案例-基本设置
-
Gridsome案例-联系我
-
Gridsome案例-部署Strapi
- strapi > configurations > Database 设置数据库 使用mysql 数据库
- 把代码传到远程仓库,登陆远程服务器拉取代码
- pm2 start npm – run start --name blog-backend
- 远程地址:1337/admin
- Gridsome案例-把本地服务联通远程Strapi
- Environment variables 配置环境变量
- 项目中访问环境变量可以混入
Vue.mixin({
data() {
return {
GRIDSOME_API_URL: process.env.GRIDSOME_API_URL
}
}
})
- Gridsome案例-部署Gridsome应用
- http;vercel
- import project 可以帮助托管部署静态应用项目
任务三:封装 Vue.js 组件库
- 课程目标
- CDD(Component-Driven Development)
- 自下而上
- 从组件级别开始,到页面级别结束
- CDD 的好处
- 组件在最大程度被重用
- 可视化测试
- 课程介绍
- 处理组件的边界情况
- 快速原型开发
- 组件开发
- Storybook 用于隔离开发组件,方便管理组件库和对组件进行测试
- Monorepo 项目结构的一种组织方式,有利于管理组件库的结构
- 基于模版生成包的结构
- Lerna + yarn workspaces 为了方便管理包的依赖,以及方便发布和提交组件
- 组件测试
- Rollup 打包
- 处理组件的边界情况
- $root 根示例,响应式
- $parent 响应式
- $children 响应式 是一个数组
- ref=“txt” $refs.txt 响应式
- 父组件提供成员provide 子组件注入成员inject,不是响应式的
- attrs-listeners
- $attrs
- 把父组件中非prop属性绑定到内部组件
- $listeners
- 把父组件中的DOM对象的原生事件绑定到内部组件
-
从父组件传给自定义自组件的属性,如果没有 prop 接收,会自动设置到子组件内部的最外层标签上,如果是class 和style 的话,会合并到最外层标签的 calss 和style
-
如果子组件中不想继承父组件传入的非prop属性,可以在export default中使用 inheritAttrs:false 禁用继承。然后通过 v-bind="$attrs" 把尾部传入的非prop属性设置给希望的标签上。但是这不会改变class 和 style
-
注册事件 < input @input=“ e m i t ( ′ i n p u t ′ , emit('input', emit(′input′,event)” @focus=“ e m i t ( ′ f o c u s ′ , emit('focus', emit(′focus′,event)”>
-
v-on="$listeners" 展开父组件传递给自组件的多个事件
-
快速原型开发
- VueCLI 中提供了一个插件可以进行原型快速开发
- 需要先额外安装一个全局的扩展
- npm install -g @vue/cli-service-global
- 使用vue serve 快速查看组件的运行效果
- vue serve
- vue serve 如果不指定参数默认会在当前目录找以下的入口文件
- main.js、index.js、App.vue、app.vue
- 可以指定要加载的组件
- vue serve ./src/login.vue
- 快速原型开发-ElementUI
- 安装ElementUI
- 初始化 package.json npm init -y
- 安装 ElementUI vue add element (需要先有vue-cli)
- 加载 ElementUI, 使用Vue.use()安装插件
- main.js
import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import Login from './src/Login.vue'
Vue.use(ElementUI)
new Vue({
el: '#app',
render: h => h(Login)
})
- 组件开发-步骤条组件
- 第三方组件 基础组件 业务组件
-
组件开发-表单组件-上
-
组件开发-表单组件-下
-
组件开发-表单组件-表单验证-上
-
组件开发-表单组件-表单验证-下
- input 组件验证
- input 组件中触发自定义事件 validate
- Formitem 渲染完毕注册自定义事件 validate
- Monorepo
- 两种项目的组织方式
- Multirepo(Multiple Repository)
- 每一个包对应一个项目
- Monorepo(Monolithic Repository)
- 一个项目仓库中管理多个模块/包(package 文件夹下放所有包)
- Storybook上 (故事书)
- 可视化的组件展示平台
- 在隔离的开发环境中,以交互式的方式展示组件
- 独立开发组件
- 支持框架
- React、React Native、Vue、Angular
- Ember、HTML、Svelte、Mithril、Riot
- Storybook 安装
- 自动安装
- npx -p @storybook/cli sb init --type vue
- yarn add vue
- yarn add vue-loader vue-template-compiler --dev
- 手动安装
- 自动安装
-
Storybook下
-
yarn workspaces
- 开启yarn 的工作区 (npm 不支持,如果多个包引用相同的依赖,会自动把依赖的包提升到工作区的根目录的node_modules中)
- 项目根目录的package.json
"private": true, // 作用是提交到github 或者 发布到npm的时候,禁止把当前根目录的内容提交
"workspaces": [
"packages/*" // 管理所有包的路径
]
- yarn workspaces 使用
- 给工作区根目录安装开发依赖
- yarn add jest -D -W
- 给指定工作区安装依赖
- yarn workspace lg-button(包名 在包的package.josn中) add lodash@4
- 给所有的工作区安装依赖
- yarn install
- 给工作区根目录安装开发依赖
- Lerna上
- Lerna 介绍
- Lerna是一个优化使用git和npm管理多包仓库的工作流工具
- 用于管理具有多个包的JavaScript项目
- 它可以一键把代码提交到git和npm仓库
- Larna 使用
- 全局安装 yarn global add lerna
- 初始化 lerna init
- 发布 lerna publish
- 配置发布命令 “scripts”: {“lerna”: “lerna publish”}
- 在github上创建一个仓库
- 必须保证npm 登陆,npm whoami 查看npm 登陆用户名
- 发布前必须检查镜像地址是npm的 npm config get registry
- yarn lerna
-
Lerna下
-
Vue组件的单元测试
- 组件单元测试好处
- 提供描述组件行为的文档
- 节省手动测试的时间
- 减少研发新特性时产生的bug
- 改进设计
- 促进重构
- 安装依赖
- Vue Test Utils vue官方提供的组件单元测试的官方库,需要结合一个单元测试框架一起使用
- Jest feesbook出的,它和vue的结合比较方便,配置最少。jest可以进行单元测试,但并不支持单文件组件,需要一个预处理器,把单文件组件编译后的结果,也就是js代码交给jest处理
- vue-jest vue官方提供的为jest服务的预处理器
- babel-jest 测试文件中,会使用到ESM 语法,还有一些es新特性,需要降级处理
- 安装 yarn add jest @vue/test-utils vue-jest babel-jest -D -W
- 配置测试脚本
"scripts": { "test": "jest" }
- jest.config.js
module.exports = { "testMatch": ["**/__tests__/**/*.[jt]s?(x)"], "moduleFileExtensions": [ "js", "json", // 告诉 Jest 处理 `*.vue` 文件 "vue" ], "transform": { // 用 `vue-jest` 处理 `*.vue` 文件 ".*\\.(vue)$": "vue-jest", // 用 `babel-jest` 处理 js ".*\\.(js)$": "babel-jest" } }
- Babel 配置文件
// babel.config.js module.exports = { presets: [ [ '@babel/preset-env' ] ] }
- Babel 桥接: 处理找不到Babel的问题,vue-jest 依赖的是 babel@6 而最新是7
- yarn add babel-core@bridge -D -W -D 开发依赖 -W安装在工作区根目录中
- Vue组件的单元测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-inS8QYQs-1627836997525)(./img/1627543570646.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eYmg3AfF-1627836997528)(./img/1627543619996.jpg)]
- 快照:第一次调用这个方法,会把测试内容存储到特定的文本文件中。第二次会把新生成的内容和前者进行对比,相同就显示成功
- yarn test -u 会生成一个新的快照(对比基准)
- Rollup打包上
- Rollup
- Rollup 是一个模块打包器
- Rollup 支持Tree-shaking
- 打包的结果比Webpack更小
- 开发框架/组件库的时候使用Rollup更合适
- 安装依赖
- rollup
- rollup-plugin-terser 对代码进行压缩
- rollup-plugin-vue@5.1.9 把单文件组件编译成js代码,一定要指定版本,最新版本是编译vue3
- vue-template-compiler
- rollup.config.js
import { terser } from 'rollup-plugin-terser' import vue from 'rollup-plugin-vue' module.exports = [ { input: 'index.js', output: [ { file: 'dist/index.js', format: 'es' } ], plugins: [ vue({ css: true, // Dynamically inject css as a <style> tag compileTemplate: true, // Explicitly convert template to render function }), terser() ] } ]
- 每个包package.json 配置
"main": "dist/cjs/index.js", "module": "dist/es/index.js",
- “scripts”: {“build”: “rollup -c”}
- yarn workspace 包名 build 可以在根目录直接打包某个模块
- Rollup打包下
- @rollup/plugin-json 可以让rollup把json文件作为模块加载 @rollup/plugin-node-resolve 作用是把依赖的第三方包打包进来
yarn add @rollup/plugin-json rollup-plugin-postcss @rollup/plugin-node-resolve -D -W
- 项目根目录创建 rollup.config.js
import fs from 'fs'
import path from 'path'
import json from '@rollup/plugin-json'
import vue from 'rollup-plugin-vue'
import postcss from 'rollup-plugin-postcss'
import { terser } from 'rollup-plugin-terser'
import { nodeResolve } from '@rollup/plugin-node-resolve'
const isDev = process.env.NODE_ENV !== 'production' // 开发环境开启代码压缩
// 公共插件配置
const plugins = [
vue({
// Dynamically inject css as a <style> tag
css: true,
// Explicitly convert template to render function
compileTemplate: true
}),
json(),
nodeResolve(),
postcss({
// 把 css 插入到 style 中
// inject: true,
// 把 css 放到和js同一目录
extract: true
})
]
// 如果不是开发环境,开启压缩
isDev || plugins.push(terser())
// packages 文件夹路径
const root = path.resolve(__dirname, 'packages')
module.exports = fs.readdirSync(root)
// 过滤,只保留文件夹
.filter(item => fs.statSync(path.resolve(root, item)).isDirectory())
// 为每一个文件夹创建对应的配置
.map(item => {
const pkg = require(path.resolve(root, item, 'package.json'))
return {
input: path.resolve(root, item, 'index.js'),
output: [
{
exports: 'auto',
file: path.resolve(root, item, pkg.main),
format: 'cjs'
},
{
exports: 'auto',
file: path.join(root, item, pkg.module),
format: 'es'
},
],
plugins: plugins
}
})
- 在每一个包中设置 package.json 中的 main 和 module 字段
"main": "dist/cjs/index.js",
"module": "dist/es/index.js"
- 根目录的 package.json 中配置 scripts
"build": "rollup -c"
- 设置环境变量
- yarn add cross-env -D -W 可以跨平台设置环境变量
"build:prod": "cross-env NODE_ENV=production rollup -c",
"build:dev": "cross-env NODE_ENV=development rollup -c",
- 清理
- “clean”: “lerna clean” 清理所有包中的node_modules
- yarn add rimraf -D -W 删除指定文件夹
+ 在每一个包中设置 package.json 中的 "scripts": {"del": "rimraf dist" }
+ yarn workspaces run del
- 基于模板生成组件基本结构
- 创建了Monorepo的项目结构,在一个项目中管理多个包,使用Storybook搭建项目,可以让用户快速预览组件,使用yarn workspaces管理所有包的依赖,使用Lerna发布项目,可以把每一个包发布到npm上。
- yarn add plop -W -D
- 添加模版
- 添加配置
- “script”: {“plop”: “plop”}
- 基于模板生成组件基本结构2
- 实现了一个模版生成的link 组件
- 发布
- yarn build:prod
- npm whoami
- yarn lerna