1.预购建重写导入为合法的 URL,例如 /node_modules/.vite/deps/my-dep.js?v=f3sf2ebd
以便浏览器能够正确导入它们。
因为原生 ES 导入不支持下面这样的裸模块导入:
// 裸模块导入
import xxx from "vue"
import xxx from "vue/xxx"
// 以下不是裸模块导入
import xxx from "./foo.ts"
import xxx from "/foo.ts"
我们可以理解为直接根据模块名导入的模块就是裸模块导入,而根据路径导入的不是裸模块导入。在原生js中直接裸模块导入会报错:
2.在开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块。因此,Vite 必须先将以 CommonJS 或 UMD 形式提供的依赖项转换为 ES 模块
3.为了提高后续页面的加载性能,Vite将那些具有许多内部模块的 ESM 依赖项转换为单个模块。
例如:lodash-es 有超过 600 个内置模块!当我们执行 import { debounce } from 'lodash-es' 时,浏览器同时发出 600 多个 HTTP 请求!即使服务器能够轻松处理它们,但大量请求会导致浏览器端的网络拥塞,使页面加载变得明显缓慢。
通过将 lodash-es 预构建成单个模块,现在我们只需要一个HTTP请求!
我们可以自己去引入这个模块中的包:然后查看浏览器中的network中的请求非常之多
import lodashES from '../node_modules/lodash-es/lodash.js'
依赖预构建仅适用于开发模式,并使用 esbuild 将依赖项转换为 ES 模块。在生产构建中,将使用@rollup/plugin-commonjs。
我们可以通过optimizeDeps.exclude和optimizeDeps.include配置来配置我们的某个包是否被预构建
一个项目中,存在非常多的模块,并不是所有模块都会被预构建。只有 bare module imports(裸模块导入)会执行依赖预构建,一般是 npm 安装的模块,是第三方的模块,不是我们自己写的代码,一般情况下是不会被修改的,因此对这部分的模块提前执行构建,有利于提升性能。注意:monorepo下的模块不会被预购建,会将该链接的依赖视为源码,也就是开发者自己写的代码。
找寻依赖的过程:在当前目录下的 node_modules 下寻找,找不到则往上一级目录的 node_modules,直到目录为根路径,不能再往上。
Vite 将预构建的依赖项缓存到 node_modules/.vite 中。它会基于以下几个来源来决定是否需要重新运行预构建步骤:
只有在上述其中一项发生更改时,才需要重新运行预构建。 如果出于某些原因你想要强制 Vite 重新构建依赖项,你可以在启动开发服务器时指定 --force 选项,或手动删除 node_modules/.vite 缓存目录。
已预构建的依赖请求使用 HTTP 头 max-age=31536000, immutable进行强缓存,以提高开发期间页面重新加载的性能。一旦被缓存,这些请求将永远不会再次访问开发服务器。
如果安装了不同版本的依赖项(这反映在包管理器的 lockfile 中),则会通过附加版本查询自动失效。如果你想通过本地编辑来调试依赖项,您可以:
模块热替换(hot module replacement),在 webpack 的定义是:在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面。vite内置了HMR到vue、react和Preact我们不需要手动设置这些 —— 当你通过 create-vite创建应用程序时,所选模板已经为你预先配置了这些。
接下来我们通过pnpm create vite 来创建一个vanilla项目,利用官网的api在该项目中实现HMR:import.meta.hot.accept这个方法会为当前模块执行热替换
// counter.js中
export const count = 3
// 判断当前的环境:生产环境中有这个api而开发环境不需要用到MHR,也就没有该api
if(import.meta.hot) {
// newModule 为新模块(变化后的counter.js)的引用,当模块变化后就会执行accept中的这个回调
import.meta.hot.accept((newModule) => {
console.log(newModule.count);
})
}
// main.js
import {count} from './counter.js'
当我们更改counter.js 中的 count 时保存后,页面并不会刷新,而是执行accept中的回调,并且控制台会有 hmr 的标识。
接下来我们更改counter.js中的代码如下:(main.js不变)
let mes = 'hello!!'
export let count = 5
setInterval(()=>{
console.log(count++);
}, 1000)
if (import.meta.hot) {
import.meta.hot.accept(newModule => {})
}
当我们运行该程序的时候控制台会一直打印count++,当我们更改mes的值后,会发现控制台会执行两个定时器,分别是新/旧模块的定时器,由此可见开启定时器是一个副作用。我们可以通过如下方式清除这个副作用:import.meta.hot.dispose方法用来清除副作用
let mes = 'hello!'
export let count = 5
let timer = setInterval(() => {
console.log(count++)
}, 1000)
if (import.meta.hot) {
import.meta.hot.accept(newModule => {})
import.meta.hot.dispose((data) => {
// 清除副作用
if(timer) clearInterval(timer)
})
}
这里只列举出来HMR一部分的api其他的api大家可以去官网查看。HMR API | Vite 官方中文文档
PostCSS是一个工具,里面包含了很多插件可以让我们更加方便书写css。官网链接:https://github.com/postcss/postcss#usage
如果项目包含有效的 PostCSS 配置 (任何受 postcss-load-config 支持的格式,例如 postcss.config.js),它将会自动应用于所有已导入的 CSS。
这里我是用 postcss-nested 插件来举个例子:
module.exports = {
plugins: [
require('postcss-nested')
]
}
注意:此时我们的post.config.js里面使用的时CommonJS规范,而vite默认支持ES Module所以我们要将postcss.config.js后缀改为 .cjs
2.@import
内联和变基可以在css中通过 @import url() 来引用其他目录下的css,我们也可以为某个目录创建别名,如下:
export default defineConfig({
resolve: {
alias: {
'@style': './public/styles'
}
}
})
在协同开发时,可能会出现相同的类名,该类名对应的样式就有可能被覆盖,所以就可以使用CSS Modules。
任何以 .module.css 为后缀名的 CSS 文件都被认为是一个 CSS modules 文件。导入这样的文件会返回一个相应的模块对象:
// index.module.css
.content {
width: 100px;
height: 100px;
background-color: red;
}
// main.js
import indexCss from '../public/styles/cssModules/index.module.css'
document.getElementById('foo').className = indexCss.content
原理:将css中的类名按照某种规则进行替换,例如我们代码中的 content -> _content_1xgol_1 ;创建映射对象,存储着类名被替换前后的映射本代码中的 indexCss就可以理解成映射对象;创建 style 标签,将替换类名后的 CSS 样式复制到里面 将 style 标签插入 index.html 的 head 标签中。
由于 Vite 的目标仅为现代浏览器,因此建议使用原生 CSS 变量和实现 CSSWG 草案的 PostCSS 插件(例如 postcss-nesting)来编写简单的、符合未来标准的 CSS。 话虽如此,但 Vite 也同时提供了对 .scss, .sass, .less, .styl 和 .stylus 文件的内置支持。没有必要为它们安装特定的 Vite 插件,但必须安装相应的预处理器依赖:
# .scss and .sass
npm add -D sass
# .less
npm add -D less
# .styl and .stylus
npm add -D stylus
当我们在js文件或者css文件(url())中引入静态资源时,会返回一个解析后的公共路径,行为类似于 Webpack 的 file-loader,例如:
在development中是 /src/assets/tup.jpg 在production是 /assets/tup-xxxx.jpg这个地址。vite会在生产构建后自动帮我们转换,并生成一个hash名。
常见的图像、媒体和字体文件类型被自动检测为资源。你可以使用 assetsInclude 选项 扩展内部列表。具体细节大家可以查看官网:静态资源处理 | Vite 官方中文文档
未被包含在内部列表或 assetsInclude 中的资源,可以使用 ?url 后缀显式导入为一个 URL。这十分有用,他是资源静态资源默认的一种引入方式。
资源可以使用 ?row 后缀声明作为字符串引入。
我们可以通过该方式动态引入静态资源:
function getImageUrl(name) {
return new URL(`./dir/${name}.png`, import.meta.url).href
}
// worker.js
var i = 0
// 收到主线程发来的消息
self.onmessage = (res) => {
console.log(res, 'res')
if(res.data.type = 'ready') {
console.log('worker线程收到消息');
}
}
// 将信息发送给主线程
self.postMessage(i)
你可以在导入请求上添加 ?worker 或 ?sharedworker 查询参数来直接导入一个 web worker 脚本。默认导出会是一个自定义 worker 的构造函数:
// main.js中
import CountWorker from './worker.js?worker'
const worker = new CountWorker()
worker.postMessage({type: 'ready', message: '主线程发来消息'})
worker.onmessage = (res) => {
console.log('主线程收到消息', res.data);
}
一个 Web Worker 可以使用 new Worker() 和 new SharedWorker() 导入。与 worker 后缀相比,这种语法更接近于标准,是创建 worker 的 推荐 方式。
// main.js
const worker = new Worker(new URL('./worker.js', import.meta.url))
worker.postMessage({type: 'ready', message: '主线程发来消息'})
worker.onmessage = (res) => {
console.log('主线程收到消息', res.data);
}
具体用法及描述请参考官网:功能 | Vite 官方中文文档
// 导入json
import json from './src/test.json'
import {name, age} from './src/test.json'
console.log(json, name, age);
// 不添加第二个参数默认为动态导入
const modules = import.meta.glob('./src/*.js')
console.log(modules);
// {./src/a.js: ƒ, ./src/b.js: ƒ}
// eager:true 表示直接引入所有模块
const modules2 = import.meta.glob('./src/*.js', { eager: true })
console.log(modules2);
// {./src/a.js: Module, ./src/b.js: Module}
关于这一部分大家可以自行查看官网的描述:
构建生产版本 | Vite 官方中文文档