对 Svelte 框架有所耳闻的朋友可能都听说过:和 React 或 Vue 不同, Svelte 并不采用 Virtual DOM,而是在部署前将代码编译为原生的 DOM 操作和 vanilla js,从而在运行 Web 应用的时候不需要依赖框架本身的运行时,也不需要做 diff/patch 这样的操作,从而提升运行速度,并降低包的大小。(对 svelte 的简单解读可以看看尤大的知乎回答)
根据上面的这个简介,一个很自然的问题就是,我们该怎么进行这个所谓的编译呢?官方并没有直接给出回答,而是推荐我们使用官网上的 REPL,一个根据输入代码实时渲染页面的在线编辑器(类似一个绑定了 Svelte 的 CodePen)。在 REPL 页面上制作出预想的效果后,下载为 svelte-app.zip
,解压后运行:
cd /path/to/svelte-app
npm install
npm run dev
就能在 localhost:5000
访问到 REPL 的结果了。
REPL 隐藏了 Svelte 的底层机制,让用户只用专注于写 .svelte
文件就好,的确很方便。不过为了知其然也要知其所以然,今天就让我们看看从 REPL 下载下来的 Svelte 模板项目是什么样的。
Svelte 模板的文件夹结构
直接下载 REPL 的初始应用,也就是上面图片中的 Hello World,得到的文件夹有这样的结构:
svelte-app
├── .gitignore
├── README.md
├── package.json
├── rollup.config.js
├── public
│ ├── favicon.png
│ ├── global.css
│ └── index.html
├── scripts
| └── setupTypeScript.js
└── src
├── App.svelte
└── main.js
因为本文的重点不是 Svelte 的 TypeScript 用法,所以我们可以不考虑 scripts
文件夹,再删去几个和代码没什么关系的文件,模板就简化为了:
svelte-app
├── package.json
├── rollup.config.js
├── public
│ ├── favicon.png
│ ├── global.css
│ └── index.html
└── src
├── App.svelte
└── main.js
熟悉 React 的朋友应该感觉这个结构很眼熟,和 create-react-app
生成的模板一样,有 src
和 public
两个子目录。
src
src
文件夹里面有 Svelte 的代码。其中 App.svelte
里面的内容和上面截图中的一致:
<script>
let name = 'world';
</script>
<h1>Hello {name}!</h1>
main.js
则是把 App
连接到最终页面上的桥梁:
import App from './App.svelte';
var app = new App({
target: document.body
});
export default app;
这里的 target: document.body
就是把 App
渲染为 body
的子节点。
public
public
文件夹则是包含了最终的 HTML 页面。其中的 favicon.png
是一个 svelte 图标,global.css
是默认的全局 CSS,我们主要来看一下 index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>Svelte app</title>
<link rel='icon' type='image/png' href='/favicon.png'>
<link rel='stylesheet' href='/global.css'>
<link rel='stylesheet' href='/build/bundle.css'>
<script defer src='/build/bundle.js'></script>
</head>
<body>
</body>
</html>
有两个值得关注的点。首先 index.html
中的 body
是空的,上文提到的 src/main.js
会用框架填充内容。其次则是下面的两行:
<link rel='stylesheet' href='/build/bundle.css'>
<script defer src='/build/bundle.js'></script>
它们引用了目前还不存在的 bundle.css
与 bundle.js
。这两个文件就是就是编译的结果了,可以猜测 Svelte 模板会把 src
中的代码编译进 public/build
文件夹中。
RollUp 是如何构建 Svelte 应用的
知道了编译的起始点(src
) 和终点(public/build
),那么编译器是如何完成这个转化的呢?根据启动项目的指令 npm run dev
,让我们来看看对应的 package.json
:
{
"name": "svelte-app",
"version": "1.0.0",
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w",
"start": "sirv public"
},
...
}
npm run dev
相当于 rollup -c -w
,也就是说 Svelte 利用的是 RollUp 进行编译和打包的。给不了解的朋友们介绍一下,RollUp 是一个 JavaScript 打包器,类似 Webpack,只是 RollUp 专注于打包 JavaScript。rollup -c -w
会让 RollUp 执行打包操作,其中 -c
指使用项目中的 rollup.config.js
作为配置文件,-w
则表示监听待打包的文件们,一旦有改动,就会自动重新打包。
所以执行 Svelte 的编译,以及最后在本地托管静态网页的秘密都在 rollup.config.js
中了。我们先把整个文件列在这里(对默认的英文注释进行了翻译):
import svelte from 'rollup-plugin-svelte';
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import css from 'rollup-plugin-css-only';
const production = !process.env.ROLLUP_WATCH;
function serve() {
let server;
function toExit() {
if (server) server.kill(0);
}
return {
writeBundle() {
if (server) return;
server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
stdio: ['ignore', 'inherit', 'inherit'],
shell: true
});
process.on('SIGTERM', toExit);
process.on('exit', toExit);
}
};
}
export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/build/bundle.js'
},
plugins: [
svelte({
compilerOptions: {
// 在非生产环境中开启运行时检查
dev: !production
}
}),
// 将所有组件中的 CSS 提取进一个文件——提高性能
css({ output: 'bundle.css' }),
// 如果你有从 npm 安装的外部依赖,那么你一般都需要这些插件。
// 一些情况下你需要进行额外的配置——详情见文档:
// https://github.com/rollup/plugins/tree/master/packages/commonjs
resolve({
browser: true,
dedupe: ['svelte']
}),
commonjs(),
// 在非生产环境中,在打包生成后运行 `npm run start`
!production && serve(),
// 在非生产环境中,在 `public` 目录中有改动时刷新浏览器
!production && livereload('public'),
// 如果为生产环境进行构建(npm run build 而不是 npm run dev),
// minify
production && terser()
],
watch: {
clearScreen: false
}
};
rollup.config.js 通用结构
让我们先挑出 RollUp 配置的通用结构,也就是:
export default {
input: 'src/main.js', // 输入文件为 src/main.js
output: {
sourcemap: true,
// 把生成的代码为 (function () { code })() 形式
// 详见:https://en.wikipedia.org/wiki/Immediately_invoked_function_expression
format: 'iife',
name: 'app',
// 输出至 public/build/bundle.js
file: 'public/build/bundle.js'
},
plugins: [
...
],
watch: {
// 在重新打包的时候不要清空终端
clearScreen: false
}
};
这部分代码印证了上面我们的猜测,也就是 RollUp 会把 src
中的文件打包至 public/build/bundle.js
。不过这里只提到了 main.js
,.svelte
文件该怎么办呢?这就要取决于 Svelte 模板使用的 RollUp 插件了。
RollUp 插件
export default {
...
plugins: [
svelte({
compilerOptions: {
// 在非生产环境中开启运行时检查
dev: !production
}
}),
// 将所有组件中的 CSS 提取进一个文件——提高性能
css({ output: 'bundle.css' }),
// 如果你有从 npm 安装的外部依赖,那么你一般都需要这些插件。
// 一些情况下你需要进行额外的配置——详情见文档:
// https://github.com/rollup/plugins/tree/master/packages/commonjs
resolve({
browser: true,
dedupe: ['svelte']
}),
commonjs(),
// 在非生产环境中,在打包生成后运行 `npm run start`
!production && serve(),
// 在非生产环境中,在 `public` 目录中有改动时刷新浏览器
!production && livereload('public'),
// 如果为生产环境进行构建(npm run build 而不是 npm run dev),
// minify
production && terser()
],
...
};
插件会根据定义的顺序依次执行。这里我们可以把插件分为 3 组。
1. Svelte 编译插件
import svelte from 'rollup-plugin-svelte';
import css from 'rollup-plugin-css-only';
export default {
...
plugins: [
svelte({
compilerOptions: {
// 在非生产环境中开启运行时检查
dev: !production
}
}),
// 将所有组件中的 CSS 提取进一个文件——提高性能
css({ output: 'bundle.css' }),
...
],
...
};
rollup-plugin-svelte
会调用 Svelte 编译器编译,由于篇幅所限,我会在后续的文章中给大家详细拆解一下这个插件。目前大家只需要知道:
- 在默认配置下,
rollup-plugin-svelte
会编译所有的.svelte
文件; rollup-plugin-svelte
先后使用了svelte
包中的preprocess
和compiler
函数。
rollup-plugin-css-only
则是一个和 Svelte 不直接相关的插件,但是因为 Svelte 编译器会单独处理每个组件的 CSS 片段,所以需要使用这个插件把 CSS 合并起来。
2. 通用插件
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
export default {
...
plugins: [
...
// 如果你有从 npm 安装的外部依赖,那么你一般都需要这些插件。
// 一些情况下你需要进行额外的配置——详情见文档:
// https://github.com/rollup/plugins/tree/master/packages/commonjs
resolve({
browser: true,
dedupe: ['svelte']
}),
commonjs(),
...
],
...
};
第二组是 RollUp 的 2 个通用插件,resolve
是为了把依赖项打进包里,commonjs
则是为了把 CommonJS 模块转化为 ES2015 供 RollUp 处理。对于这两个插件的用例介绍,请见 RollUp 文档。
3. 调试环境与生产环境用插件
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
const production = !process.env.ROLLUP_WATCH;
export default {
...
plugins: [
...
// 在非生产环境中,在打包生成后运行 `npm run start`
!production && serve(),
// 在非生产环境中,在 `public` 目录中有改动时刷新浏览器
!production && livereload('public'),
// 如果为生产环境进行构建(npm run build 而不是 npm run dev),
// minify
production && terser()
],
...
};
在模板项目的配置文件中,通过查看是否启用了 RollUp 的监听功能(ROLLUP_WATCH
)来判断当前是否处于生产环境。最后一组的 3 个插件则和是否处于生产环境息息相关。注释已经把 livereload
和 terser
的作用讲得很清楚了,就不再赘述。我们来看看 serve
这个插件。它是唯一一个在配置文件中编写的插件,也是负责托管静态网页的重要角色。它的源码如下:
function serve() {
let server;
function toExit() {
if (server) server.kill(0);
}
return {
writeBundle() {
if (server) return;
server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
stdio: ['ignore', 'inherit', 'inherit'],
shell: true
});
process.on('SIGTERM', toExit);
process.on('exit', toExit);
}
};
}
serve
使用了 RollUp 的一个钩子函数 writeBundle
,这个函数会在打包完成的时候被调用。对于 serve
来说,就是在打包完成的时候启动(spawn
)一个新进程,运行 npm run start
。对应 package.json
的话就是运行:
sirv public
sirv 是一个用来托管静态文件的 cli 应用,Svelte 模板就是利用它来完成最后的预览工作的。
到这里我们就介绍完了 Svelte 模板了~ 到这里你是不是对 Svelte REPL 生成的模板应用多了一份了解呢?在后续的文章中,我会再进一步,聊一聊 rollup-plugin-svelte
里面都做了什么,敬请期待~ 如果喜欢本文的话,也别忘了点个赞哦!
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!