范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

使用Vite和TypeScript带你从零打造一个属于自己的Vue3组件库

  前言
  随着前端技术的发展,业界涌现出了许多的UI组件库。例如我们熟知的ElementUI,Vant,AntDesign等等。但是作为一个前端开发者,你知道一个UI组件库是如何被打造出来的吗?
  读完这篇文章你将学会: 如何使用pnpm搭建出一个Monorepo环境 如何使用vite搭建一个基本的Vue3脚手架项目 如何开发调试一个自己的UI组件库 如何使用vite打包并发布自己的UI组件库
  作为一个前端拥有一个属于自己的UI组件库是一件非常酷的事情。它不仅能让我们对组件的原理有更深的理解,还能在找工作的时候为自己增色不少。试问有哪个前端不想拥有一套属于自己的UI组件库呢?
  本文将使用Vue3和TypeScript来编写一个组件库,使用Vite+Vue3来对这个组件库中的组件进行调试,最后使用vite来对组件库进行打包并且发布到npm上。最终的产物是一个名为kitty-ui的组件库。
  话不多说~ 接下来让我们开始搭建属于我们自己的UI组件库吧 Monorepo环境
  首先我们要了解什么是Monorepo及它是如何搭建的吧
  就是指在一个大的项目仓库中,管理多个模块/包(package),这种类型的项目大都在项目根目录下有一个packages文件夹,分多个项目管理。大概结构如下: -- packages   -- pkg1     --package.json   -- pkg2     --package.json --package.json
  简单来说就是 单仓库 多项目
  目前很多我们熟知的项目都是采用这种模式,如Vant,ElementUI,Vue3等。打造一个Monorepo环境的工具有很多,如:lerna、pnpm、yarn等,这里我们将使用pnpm来开发我们的UI组件库。
  为什么要使用pnpm?
  因为它简单高效,它没有太多杂乱的配置,它相比于lerna操作起来方便太多
  好了,下面我们就开始用pnpm来进行我们的组件库搭建吧 使用pnpm安装npm install pnpm -g 初始化package.jsonpnpm init 新建配置文件 .npmrcshamefully-hoist = true
  这里简单说下为什么要配置 shamefully-hoist 。
  如果某些工具仅在根目录的 node_modules 时才有效,可以将其设置为true来提升那些不在根目录的 node_modules ,就是将你安装的依赖包的依赖包的依赖包的...都放到同一级别(扁平化)。说白了就是不设置为true有些包就有可能会出问题。 monorepo的实现
  接下就是pnpm如何实现monorepo的了。
  为了我们各个项目之间能够互相引用我们要新建一个 pnpm-workspace.yaml 文件将我们的包关联起来 packages:     - "packages/**"     - "examples"
  这样就能将我们项目下的packages目录和examples目录关联起来了,当然如果你想关联更多目录你只需要往里面添加即可。根据上面的目录结构很显然你在根目录下新packages和examples文件夹,packages文件夹存放我们开发的包,examples用来调试我们的组件
  examples文件夹就是接下来我们要 使用vite搭建一个基本的Vue3脚手架项目 的地方 安装对应依赖
  我们开发环境中的依赖一般全部安装在整个项目根目录下,方便下面我们每个包都可以引用,所以在安装的时候需要加个  -w pnpm i vue@next typescript less -D -w
  因为我们开发的是vue3组件, 所以需要安装vue3,当然ts肯定是必不可少的(当然如果你想要js开发也是可以的,甚至可以省略到很多配置和写法。但是ts可以为我们组件加上类型,并且使我们的组件有代码提示功能,未来ts也将成为主流);less为了我们写样式方便,以及使用它的命名空间(这个暂时这里没用到,后面有时间再补 配置tsconfig.json
  这里的配置就不细说了,可以自行搜索都是代表什么意思。或者你可以先直接复制 npx tsc --init
  tsconfig.json: {   "compilerOptions": {     "baseUrl": ".",     "jsx": "preserve",     "strict": true,     "target": "ES2015",     "module": "ESNext",     "skipLibCheck": true,     "esModuleInterop": true,     "moduleResolution": "Node",     "lib": ["esnext", "dom"]   } } 手动搭建一个基于vite的vue3项目
  其实搭建一个vite+vue3项目是非常容易的,因为vite已经帮我们做了大部分事情 初始化仓库
  进入examples文件夹,执行 pnpm init 安装vite和@vitejs/plugin-vue
  @vitejs/plugin-vue用来支持.vue文件的转译 pnpm install vite @vitejs/plugin-vue -D -w
  这里安装的插件都放在根目录下 配置vite.config.ts
  新建vite.config.ts import { defineConfig } from "vite" import vue from "@vitejs/plugin-vue"  export default defineConfig({     plugins:[vue()] })  新建html文件
  @vitejs/plugin-vue 会默认加载examples下的index.html
  新建index.html                       Document              
  注意:  vite 是基于esmodule的 所以type="module" 新建app.vue模板 新建main.tsimport {createApp} from "vue" import App from "./app.vue"  const app = createApp(App)  app.mount("#app")
  此时会发现编译器会提示个错误:找不到模块"./app.vue"或其相应的类型声明
  因为直接引入.vue文件 TS会找不到对应的类型声明;所以需要新建typings(命名没有明确规定,TS会自动寻找.d.ts文件)文件夹来专门放这些声明文件。
  typings/vue-shim.d.ts
  TypeScriptTS默认只认ES 模块。如果你要导入.vue文件就要declare module把他们声明出来。 declare module "*.vue" {     import type { DefineComponent } from "vue";     const component:DefineComponent<{},{},any> } 配置脚本启动项目
  最后在package.json文件中配置scripts脚本 ... "scripts": {     "dev": "vite"   }, ...
  然后终端输入我们熟悉的命令:pnpm run dev
  vite启动默认端口为3000;在浏览器中打开localhost:3000 就会看我们的"启动测试"页面。 本地调试新建包文件
  本节可能和目前组件的开发关联不大,但是未来组件需要引入一些工具方法的时候会用到
  接下来就是要往我们的packages文件夹冲填充内容了。 utils包
  一般packages要有utils包来存放我们公共方法,工具函数等
  既然它是一个包,所以我们新建utils目录后就需要初始化它,让它变成一个包;终端进入utils文件夹执行:pnpm init 然后会生成一个package.json文件;这里需要改一下包名,我这里将name改成@kitty-ui/utils表示这个utils包是属于kitty-ui这个组织下的。所以记住发布之前要登录npm新建一个组织;例如kitty-ui {   "name": "@kitty-ui/utils",   "version": "1.0.0",   "description": "",   "main": "index.ts",   "scripts": {     "test": "echo "Error: no test specified" && exit 1"   },   "keywords": [],   "author": "",   "license": "ISC" }
  因为我们使用ts写的,所以需要将入口文件index.js改为index.ts,并新建index.ts文件:(先导出一个简单的加法函数) export const testfun = (a:number,b:number):number=>{     return a + b } 组件库包(这里命名为kitty-ui)
  components是我们用来存放各种UI组件的包
  新建components文件夹并执行 pnpm init 生成package.json {   "name": "kitty-ui",   "version": "1.0.0",   "description": "",   "main": "index.ts",   "scripts": {     "test": "echo "Error: no test specified" && exit 1"   },   "keywords": [],   "author": "",   "license": "ISC" }
  新建index.ts入口文件并引入utils包 import {testfun} from "@kitty-ui/utils"  const result = testfun (1,1)  console.log(result) esno
  由于组件库是基于ts的,所以需要安装esno来执行ts文件便于测试组件之间的引入情况
  控制台输入 esno xxx.ts 即可执行ts文件 npm i esno -g 包之间本地调试
  进入components文件夹执行 pnpm install @kitty-ui/utils
  你会发现pnpm会自动创建个软链接直接指向我们的utils包;此时components下的packages: {   "name": "kitty-ui",   "version": "1.0.0",   "description": "",   "main": "src/index.ts",   "scripts": {     "test": "echo "Error: no test specified" && exit 1"   },   "keywords": [],   "author": "",   "license": "ISC",   "dependencies": {     "@kitty-ui/utils": "workspace:^1.0.1"   } }
  你会发现它的依赖@kitty-ui/utils对应的版本为:workspace:^1.0.0;因为pnpm是由workspace管理的,所以有一个前缀workspace可以指向utils下的工作空间从而方便本地调试各个包直接的关联引用。
  到这里基本开发方法我们已经知道啦;接下来就要进入正题了,开发一个button组件 试着开发一个button组件
  在components文件夹下新建src,同时在src下新建button组件目录和icon组件目录(新建icon为了便于调试);此时components文件目录如下 -- components   -- src     -- button     -- icon     -- index.ts -- package.json
  让我们先测试一下我们的button组件能否在我们搭建的examples下的vue3项目本引用~
  首先在button下新建一个简单的button.vue 
  然后在button/index.ts将其导出 import Button from "./button.vue"  export default Button
  因为我们开发组件库的时候不可能只有button,所以我们需要一个components/index.ts将我们开发的组件一个个的集中导出 import Button from "./button"  export {     Button }
  好了,一个组件的大体目录差不多就是这样了,接下来请进入我们的examples来看看能否引入我们的button组件 vue3项目使用button
  上面已经说过执行在workspace执行 pnpm i xxx的时候pnpm会自动创建个软链接直接指向我们的xxx包。
  所以这里我们直接在examples执行:pnpm i kitty-ui
  此时你就会发现packages.json的依赖多了个 "kitty-ui": "workspace:^1.0.0"
  这时候我们就能直接在我们的测试项目下引入我们本地的components组件库了,启动我们的测试项目,来到我们的  examples/app.vue  直接引入Button 
  不出意外的话你的页面就会展示我们刚刚写的button组件了
  好了万事具...(其实还差个打包,这个后面再说~);接下来的工作就是专注于组件的开发了;让我们回到我们的button组件目录下(测试页面不用关,此时我们已经可以边开发边调试边看效果了)
  因为我们的button组件是需要接收很多属性的,如type、size等等,所以我们要新建个types.ts文件来规范这些属性
  在button目录下新建types.ts   import { ExtractPropTypes } from "vue"   export const ButtonType = ["default", "primary", "success", "warning", "danger"]  export const ButtonSize = ["large", "normal", "small", "mini"];   export const buttonProps = {   type: {     type: String,     values: ButtonType   },   size: {     type: String,     values: ButtonSize   } }  export type ButtonProps = ExtractPropTypes
  TIPS
  import type 表示只导入类型;ExtractPropTypes是vue3中内置的类型声明,它的作用是接收一个类型,然后把对应的vue3所接收的props类型提供出来,后面有需要可以直接使用
  很多时候我们在vue中使用一个组件会用的app.use 将组件挂载到全局。要使用app.use函数的话我们需要让我们的每个组件都提供一个install方法,app.use()的时候就会调用这个方法;
  我们将button/index.ts调整为 import button from "./button.vue" import type {App,Plugin} from "vue" type SFCWithInstall = T&Plugin const withInstall = (comp:T) => {     (comp as SFCWithInstall).install = (app:App)=>{         //注册组件         app.component((comp as any).name,comp)     }     return comp as SFCWithInstall } const Button = withInstall(button) export default Button
  此时我们就可以使用app.use来挂载我们的组件啦
  其实withInstall方法可以做个公共方法放到工具库里,因为后续每个组件都会用到,这里等后面开发组件的时候再调整
  到这里组件开发的基本配置已经完成,最后我们对我们的组件库以及工具库进行打包,打包之前如果要发公共包的话记得将我们的各个包的协议改为MIT开源协议 ... "license": "MIT", ... vite打包配置文件
  打包们这里选择vite,它有一个库模式专门为我们来打包这种库组件的。
  前面已经安装过vite了,所以这里直接在components下直接新建vite.config.ts(配置参数文件中已经注释):  import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue" export default defineConfig(     {         build: {             target: "modules",             //打包文件目录             outDir: "es",             //压缩             minify: false,             //css分离             //cssCodeSplit: true,             rollupOptions: {                 //忽略打包vue文件                 external: ["vue"],                 input: ["src/index.ts"],                 output: [                     {                         format: "es",                         //不用打包成.es.js,这里我们想把它打包成.js                         entryFileNames: "[name].js",                         //让打包目录和我们目录对应                         preserveModules: true,                         //配置打包根目录                         dir: "es",                         preserveModulesRoot: "src"                     },                     {                         format: "cjs",                         entryFileNames: "[name].js",                         //让打包目录和我们目录对应                         preserveModules: true,                         //配置打包根目录                         dir: "lib",                         preserveModulesRoot: "src"                     }                 ]             },             lib: {                 entry: "./index.ts",                 formats: ["es", "cjs"]             }         },         plugins: [             vue()         ]     } )
  这里我们选择打包cjs(CommonJS)和esm(ESModule)两种形式,cjs模式主要用于服务端引用(ssr),而esm就是我们现在经常使用的方式,它本身自带treeShaking而不需要额外配置按需引入(前提是你将模块分别导出),非常好用~
  其实到这里就已经可以直接打包了;components下执行: pnpm run build你就会发现打包了es和lib两个目录
  kitty_1.jpg
  到这里其实打包的组件库只能给js项目使用,在ts项目下运行会出现一些错误,而且使用的时候还会失去代码提示功能,这样的话我们就失去了用ts开发组件库的意义了。所以我们需要在打包的库里加入声明文件(.d.ts)。
  那么如何向打包后的库里加入声明文件呢? 其实很简单,只需要引入 vite-plugin-dts pnpm i vite-plugin-dts -D -w
  然后修改一下我们的vite.config.ts引入这个插件 import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue" import dts from "vite-plugin-dts"  export default defineConfig(     {         build: {...},         plugins: [             vue(),             dts({                 //指定使用的tsconfig.json为我们整个项目根目录下掉,如果不配置,你也可以在components下新建tsconfig.json                 tsConfigFilePath: "../../tsconfig.json"             }),             //因为这个插件默认打包到es下,我们想让lib目录下也生成声明文件需要再配置一个             dts({                 outputDir:"lib",                 tsConfigFilePath: "../../tsconfig.json"             })          ]     } )
  因为这个插件默认打包到es下,我们想让lib目录下也生成声明文件需要再配置一个dts插件,暂时没有想到其它更好的处理方法~
  然后执行打包命令你就会发现你的es和lib下就有了声明文件
  其实后面就可以进行发布了,发布之前更改一下我们components下的package.json如下: {   "name": "kitty-ui",   "version": "1.0.0",   "main": "lib/index.js",   "module":"es/index.js",   "scripts": {     "build": "vite build"   },   "files": [     "es",     "lib"   ],   "keywords": [     "kitty-ui",     "vue3组件库"   ],   "author": "小月",   "license": "MIT",   "description": "",   "typings": "lib/index.d.ts" }
  解释一下里面部分字段
  pkg.module
  我们组件库默认入口文件是传统的CommonJS模块,但是如果你的环境支持ESModule的话,构建工具会优先使用我们的module入口
  pkg.files
  files是指我们1需要发布到npm上的目录,因为不可能components下的所有目录都被发布上去 开始发布
  如果打包和发布流程你想做到自动化控制,你可以先点个赞,然后直接阅读  从0搭建Vue3组件库:使用gulp自动化处理打包与发布[1]  当然你也可以继续往下阅读,看下本篇文章的处理方式
  做了那么多终于到发布的阶段了;其实npm发包是很容易的,就拿我们的组件库kitty-ui举例吧
  发布之前记得到 npm[2] 官网注册个账户,如果你要发布@xx/xx这种包的话需要在npm新建个组织组织组织名就是@后面的,比如我建的组织就是kitty-ui,注册完之后你就可以发布了
  首先要将我们代码提交到git仓库,不然pnpm发布无法通过,后面每次发版记得在对应包下执行  pnpm version patch 你就会发现这个包的版本号patch(版本号第三个数) +1 了,同样的  pnpm version major  major和  pnpm version minor  分别对应版本号的第一和第二位增加。
  如果你发布的是公共包的话,在对应包下执行 pnpm publish --access public
  输入你的账户和密码(记得输入密码的时候是不显示的,不要以为卡了)正常情况下应该是发布成功了
  注意
  发布的时候要将npm的源切换到npm的官方地址( registry.npmjs.org/[3] ); 如果你使用了其它镜像源的话样式问题
  引入我们打包后的组件你会发现没有样式,所以你需要在全局引入我们的style.css才行;如 main.ts中需要 import "kitty-ui/es/style.css";
  很显然这种组件库并不是我们想要的,我们需要的组件库是每个css样式放在每个组件其对应目录下,这样就不需要每次都全量导入我们的css样式。
  下面就让我们来看下如何把样式拆分打包 处理less文件
  首先我们需要做的是将less打包成css然后放到打包后对应的文件目录下,我们在components下新建build文件夹来存放我们的一些打包工具,然后新建buildLess.ts,首先我们需要先安装一些工具cpy和fast-glob pnpm i cpy fast-glob -D -w cpy
  它可以直接复制我们规定的文件并将我们的文件copy到指定目录,比如buildLess.ts: import cpy from "cpy" import { resolve } from "path"  const sourceDir = resolve(__dirname, "../src") //lib文件 const targetLib = resolve(__dirname, "../lib") //es文件 const targetEs = resolve(__dirname, "../es") console.log(sourceDir); const buildLess = async () => {     await cpy(`${sourceDir}/**/*.less`, targetLib)     await cpy(`${sourceDir}/**/*.less`, targetEs) } buildLess()
  然后在package.json中新增命令 ... "scripts": {     "build": "vite build",     "build:less": "esno build/buildLess"   }, ...
  终端执行  pnpm run build:less  你就会发现lib和es文件对应目录下就出现了less文件.
  但是我们最终要的并不是less文件而是css文件,所以我们要将less打包成css,所以我们需要用的less模块.在ts中引入less因为它本身没有声明文件所以会出现类型错误,所以我们要先安装它的  @types/less pnpm i --save-dev @types/less -D -w
  buildLess.ts如下(详细注释都在代码中) import cpy from "cpy" import { resolve, dirname } from "path" import { promises as fs } from "fs" import less from "less" import glob from "fast-glob" const sourceDir = resolve(__dirname, "../src") //lib文件目录 const targetLib = resolve(__dirname, "../lib") //es文件目录 const targetEs = resolve(__dirname, "../es")  //src目录  const srcDir = resolve(__dirname, "../src")  const buildLess = async () => {     //直接将less文件复制到打包后目录     await cpy(`${sourceDir}/**/*.less`, targetLib)     await cpy(`${sourceDir}/**/*.less`, targetEs)      //获取打包后.less文件目录(lib和es一样)     const lessFils = await glob("**/*.less", { cwd: srcDir, onlyFiles: true })      //遍历含有less的目录     for (let path in lessFils) {          const filePath = `${srcDir}/${lessFils[path]}`         //获取less文件字符串         const lessCode = await fs.readFile(filePath, "utf-8")         //将less解析成css          const code = await less.render(lessCode, {             //指定src下对应less文件的文件夹为目录             paths: [srcDir, dirname(filePath)]         })          //拿到.css后缀path         const cssPath = lessFils[path].replace(".less", ".css")           //将css写入对应目录         await fs.writeFile(resolve(targetLib, cssPath), code.css)         await fs.writeFile(resolve(targetEs, cssPath), code.css)     }    } buildLess()
  执行打包命令之后你会发现对应文件夹下多了.css文件
  1657259623489.jpg
  现在我已经将css文件放入对应的目录下了,但是我们的相关组件并没有引入这个css文件;所以我们需要的是每个打包后组件的index.js中出现如: import "xxx/xxx.css"
  之类的代码我们的css才会生效;所以我们需要对vite.config.ts进行相关配置
  首先我们先将.less文件忽略 external: ["vue", /.less/],
  这时候打包后的文件中如button/index.js就会出现 import "./style/index.less";
  然后我们再将打包后代码的.less换成.css就大功告成了 ... plugins: [             ...              {                 name: "style",                 generateBundle(config, bundle) {                     //这里可以获取打包后的文件目录以及代码code                     const keys = Object.keys(bundle)                      for (const key of keys) {                         const bundler: any = bundle[key as any]                         //rollup内置方法,将所有输出文件code中的.less换成.css,因为我们当时没有打包less文件                          this.emitFile({                             type: "asset",                             fileName: key,//文件名名不变                             source: bundler.code.replace(/.less/g, ".css")                         })                     }                 }             }         ...         ] ...
  我们最终的vite.config.ts如下 import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue" import dts from "vite-plugin-dts"  export default defineConfig(     {         build: {             target: "modules",             //打包文件目录             outDir: "es",             //压缩             minify: false,             //css分离             //cssCodeSplit: true,             rollupOptions: {                 //忽略打包vue和.less文件                 external: ["vue", /.less/],                 input: ["src/index.ts"],                 output: [                     {                         format: "es",                         //不用打包成.es.js,这里我们想把它打包成.js                         entryFileNames: "[name].js",                         //让打包目录和我们目录对应                         preserveModules: true,                         //配置打包根目录                         dir: "es",                         preserveModulesRoot: "src"                     },                     {                         format: "cjs",                         //不用打包成.mjs                         entryFileNames: "[name].js",                         //让打包目录和我们目录对应                         preserveModules: true,                         //配置打包根目录                         dir: "lib",                         preserveModulesRoot: "src"                     }                 ]             },             lib: {                 entry: "./index.ts",                 formats: ["es", "cjs"]             }         },             plugins: [             vue(),             dts({                 //指定使用的tsconfig.json为我们整个项目根目录下掉,如果不配置,你也可以在components下新建tsconfig.json                 tsConfigFilePath: "../../tsconfig.json"             }),             //因为这个插件默认打包到es下,我们想让lib目录下也生成声明文件需要再配置一个             dts({                 outputDir: "lib",                 tsConfigFilePath: "../../tsconfig.json"             }),              {                 name: "style",                 generateBundle(config, bundle) {                     //这里可以获取打包后的文件目录以及代码code                     const keys = Object.keys(bundle)                      for (const key of keys) {                         const bundler: any = bundle[key as any]                         //rollup内置方法,将所有输出文件code中的.less换成.css,因为我们当时没有打包less文件                          this.emitFile({                             type: "asset",                             fileName: key,//文件名名不变                             source: bundler.code.replace(/.less/g, ".css")                         })                     }                 }             }          ]     } )
  最后我们将打包less与打包组件合成一个命令(package.json): ... "scripts": {     "build": "vite build & esno build/buildLess"   }, ...
  后续直接执行pnpm run build 即可完成所有打包啦 直接使用
  如果你不想一步步的搭建,想直接使用现成的话,你可以直接把项目clone下来-> kittyui[4] ,然后你只需要以下几步便可将其完成安装pnpm npm i pnpm -g 安装esno npm i esno -g 安装所有依赖 pnpm install 本地测试 进入examples文件夹执行 pnpm run dev 启动vue3项目 打包 pnpm run build 启动文档 pnpm run docs:dev 打包文档 pnpm run docs:build 启动打包后文档服务 pnpm run docs:serve 写在最后
  由于作者水平有限,难免会存在一些错误或不妥之处,希望各位能够不吝指出,一定及时修改。如果你对这个项目有更好的想法或者建议也欢迎在评论区提出,不胜感激。
  后续我会对一些常用组件进行开发,每个组件的开发都会以文章的形式展现出来以供大家参考。如果有问题欢迎私信
  说明
  组件库最新代码导出方式写法有所改变,源码可能和文章有所出入,请注意查看

白头发越来越多怎么办?吃黑芝麻有用吗?更多健康知识,见智云健康公众号岁月是把杀猪刀,白了我的头发,肥了我的腰。人到中年,除了要面临发福脱发等问题,还要开始遭遇白头的危机。虽然并不影响身体健康,但是,白头发越多,看起来就自己泡药酒,安全吗?提醒这3类中药或自带毒性,尽量别泡酒酒是粮食精,越喝越年轻,酒水作为世界三大功能性饮料之一,距今已有几千年的发展史,无论是古代人还是现代人,对酒精都是爱不释手,随着一杯杯酒水的传递,也逐渐拉近了人与人之间的距离,使感脂肪瘤,就是脾虚!讲一个中成药,健脾化痰消瘤散结脂肪瘤,中医叫痰核,自古以来,它就叫这个名字,不信的话,大家可以去翻翻医书,历代典籍几乎都是这么记载的。一说痰核,那一定得跟痰湿扯上关系,进一步来讲,痰湿是从哪来的?痰湿是脾气不运熬夜急救,这几个方法一定要知道有的人是因为工作学习的任务过于繁重,有的人是因为日常作息养成了晚睡晚起的习惯,总之,正在阅读这篇文章的你可能正在经历熬夜。我们经常会听到好多人说熬夜不好,一些脱发皮肤暗沉记忆力差等早餐不用犯愁,教你10道早餐做法,简单又营养,天天吃着不重样本期导读做早餐不用犯愁,教你10道早餐做法,做法简单又营养,天天吃着不重样一年之计在于春,一天之计在于晨,早餐是一日三餐中,很重要的一餐,早餐要吃好,不吃早餐,对健康不利。你是否还带你认识16种不同的野菜,附带图片和吃法,若见到请珍惜宋朝文学家美食家苏轼曾在长江边这样写道春江水暖鸭先知,桃花流水鳜鱼肥,蒌蒿满地芦芽短,正是河豚欲上时!而春天早已悄然来临,也正是蒌蒿满地芦芽短的时节。长江十年禁渔期,鳜鱼,河豚是吃鸡蛋羹千万不要直接上锅蒸,多加这2个步骤,鸡蛋羹里外都很滑嫩大家好,我是大磊。鸡蛋,一直就是营养食品的代名词,特别是困难年代,国人全靠鸡蛋补充营养,特别珍贵。现在,鸡蛋却是餐桌上常见的食物,无论是炒鸡蛋蒸蛋还是茶叶蛋,吃着都非常不错,种类真猪毛别用火烤和镊子拔了,教你一个土方法,1根猪毛也不见每年元宵节,我都要坐着拔猪毛,这活又累又长,而这个猪毛怎么才能一秒拔完?10斤五花肉,我要扒到什么时候?我仰天长啸,我如同一把镊子要度过一生的感觉,正当一筹莫展的时候,奶奶起身烧起3次MVP4次最佳防守球员,库明加喊出个人目标,历史前15级别啊NBA全明星第一日的赛程已经结束,勇士队的潜力新星库明加在接受媒体采访时谈到了自己的职业生涯目标。面对镜头,库明加丝毫没有掩饰自己的雄心壮志,他表示自己的目标是至少拿到三次MVP,论吃大米的花样,全国有哪里能赢过广西?专注分享地方农特产美食世界为村,不分界邦广西人,是不会好好吃大米的!曾有东北朋友说除了东北大米,其他地方的米都不好吃!也许,他道出了个人对家乡的情感也许,也道出了很多人的心声但好吃无论在哪个时期,这4位内线玩家都能称霸,就是在小球时代也可以倘若你看过上世纪末和本世纪初的比赛,你绝对明白昔日的比赛风格和目前截然相反,当时联盟是非常注重身体对抗的,大个子玩家要做的几乎就是肉搏,利用背身单打和空接暴扣来终结进攻,用强悍的卡
RedmiK50本月推出,起步价曝光,焊死性价比红米手机是小米手机的副品牌,之前小米手机以高性价比著称,而现在小米手机已经转向高端市场,红米手机担起了高性价比的重任。红米手机在高性价比方面表现也是比较的优异,特别是红米Redmi安徽一小伙自费包大巴带村里老人春游,网友以后肯定不愁找媳妇近日,在安徽,一段小伙自费带村里老人春游的视频引发网友热议,视频显示,小伙趁着风和日丽的时候,他就想着带村里的老人去春游,为此他特意自掏腰包包了一辆大巴车,只见老人得知有人邀请他们安徽最有名的七大特色小吃,除了第四道,其它的都特别令人嘴馋去安徽游玩,除了要尝一尝大家熟知的黄山烧饼毛豆腐三河米饺虾子面这些特色小吃外,你还知道安徽还有哪些著名小吃吗?尤其是以下这七大著名的特色小吃,你都吃过吗?01。乌饭团乌饭团是安徽的巴厘岛解除隔离,重新开放23个国家的落地签证巴厘岛政府出人意料地提早宣布取消检疫规定,并为来自23个国家的旅客恢复落地签证计划。公告是一个惊喜。毫无疑问,是由于omicron病例的迅速下降,加上来自旅游业的压力和对其他已重新36岁宋仲基堪称冻龄男神,穿西装光脚丫拍写真,少年感回来了一说起宋仲基,这位韩星一直都有着颇高的人气,他与前妻宋慧乔的婚姻故事也让网友们觉得非常可惜。一部太阳的后裔让两人从戏里甜到了戏外,柳时镇与姜暮烟的爱情童话从此照进了现实,没想到双宋一个老人,如果即将归去,那他会有以下4个举动,儿女要留意01hr所谓万事枯荣皆前定。这世间的万事万物,都是有定数的。枯荣兴衰,生死存亡,一切都会轮转,一切都会在现实生活中出现。婴儿从出生的那一刻开始,就注定了他的结果,不过是大闹一场,悄人和人,不一样人和人,从来都不一样这个世界,从来没有感同身受。有人没体会过你的苦,却总在劝你大度有人没经历过你的伤,却总要求你坚强。有人活在城堡里,却问你为何要为了碎银几两,去劳累身心有人从未掏不止有K40,小米这款机型也十分能打,还有一亿像素哈曼卡顿在高通近几年发布的几款骁龙8系列芯片中,次旗舰骁龙870最为火爆,虽然性能不如骁龙888系列或者最新的骁龙8,但对于大部分玩家来说,即便是游戏派对就够了。由于不少厂商将骁龙870投今年买基金太难赚钱了买基金两三年了,从2021年12月开始,咱们A股好像中了魔咒一样。外围市场涨它跌,外围市场上跌,它大跌。真是一个付不起的阿斗啊!外围打仗,中国经济也没受大影响,可它就是跌。一跌还是我的投资流水账2022年3月7日今天让我想起2020年3月刚入场的那段日子,挺难的,直到6月情况才有所改观。我估计现在又要重现那时候的情况。没啥好想的,持仓票连个像样的反弹都没有,世上没这本书卖。股市没啥好选择的生活中,有这些爱好的人,普遍心眼很坏图源自网络侵权请联系删除朋友对于我们来说,是调剂生活,让奋斗的日子变得更有趣的一种存在。好的朋友可以给我们的人生带来积极的影响,不好的朋友却会影响你的情绪,甚至影响你的人生。所以交