✨ snowpack简介

  • ssnowpack官网

  • GitHub

snowpack,号称 无需打包工具(Webpack,Parcel)便能将代码结果实时展现在浏览器中

Snowpack最初是Fred在Google 的 Polymer 团队工作中做出来一个用于替代 HTML imports规范的构建工具,后来引申出 「为什么npm包在浏览器运行都需要借助webpack打包,而不能单独运行在浏览器呢?」 的问题,于是Fred就针对 「npm包单独运行在浏览器」 的可行性开始不断的尝试,这就有了之后的Snowpack。

在 ESM 出现之前,JavaScript 的模块化就有各式各样的规范,主要有 CommonJS, AMD, CMD, UMD 等规范,最为广泛的就是 Node.js 的 CommonJS,使用 module.exports 和 require 来导出导入模块,它是 npm 中的模块最主要提供的格式。由于浏览器并不直接支持这些模块,因此打包工具(Webpack,Browserify,Parcel 等)出现了。

  1. 在开发过程中你是否遇到 webapp 总是需要等待才能看到结果,每次保存后电脑就非常疯狂。
  2. webpack 之类的打包工具功能非常强大,他们引入配置,插件,依赖成本很低,任意创建一个 react 应用便将要安装 200M 的依赖包,并且需要写很多行的webpack配置。
  3. ESM在浏览器中使用了大约5年的时间,现在在所有现代浏览器中都受支持(可追溯到2018年初)。使用ESM,不再需要打包工具。您可以在没有 Webpack 的情况下构建一个现代化,高性能,可用于生产的Web应用程序!
  4. 你只需安装运行一次 snowpack 替换 Webpack,Parcel等繁杂的打包工具,可以获得更快的开发环境,并减少工具复杂性。

bundle与bundleless对比图:

Bundle(Webpack) Bundleless(Snowpack)
启动时间 长,完整打包项目 短,只启动 dev server,按需加载
构建时间 随项目体积线性增长 构建时间复杂度O(1)
加载性能 打包后加载对应bundle 请求映射至本地文件
缓存能力 缓存利用率一般,受spit方式影响 缓存利用率近乎完美
文件更新 重新打包 重新请求单个文件
调试体验 通常需要SourceMap进行调试 不强依赖 SourceMap,可单文件调试
生态 Webpack做的太好太强大了 不成熟,但一年时间发展迅猛

✨ 创建snowpack项目

创建一个snowpack项目,只需要安装snowpack包即可:

  1. npm init
  2. yarn add -D snowpack

package.json中添加以下脚本:

  1. {
  2. "scripts": {
  3. "start": "snowpack dev",
  4. "build": "snowpack build"
  5. },
  6. "devDependencies": {
  7. "snowpack": "^3.8.8"
  8. }
  9. }

接下来创建一个ESM的模块:

  1. export function helloWorld() {
  2. console.log('Hello World !');
  3. }

index.js中引入:

  1. import { helloWorld } from './sayHello.js';
  2. helloWorld();

再创建一个index.html中引入:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1" />
  6. <meta name="description" content="Starter Snowpack App" />
  7. <title>Starter Snowpack App</title>
  8. <script type="module" src="./js/index.js"></script>
  9. </head>
  10. <body>
  11. <div>Hello Snowpack</div>
  12. </body>
  13. </html>

通过运行命令即可开启本地服务:

  1. yarn start

默认运行在 http://localhost:8080,这是一个热更新服务器,修改代码可以直接看到效果。

使用NPM包

比如引入一个canvas-confetti

  1. yarn add canvas-confetti

修改index.js

  1. import confetti from 'canvas-confetti';
  2. confetti.create(document.getElementById('canvas'), {
  3. resize: true,
  4. useWorker: true,
  5. })({ particleCount: 200, spread: 200 });

直接保存,在浏览器就可以看到效果。

使用CSS

可以跟正常使用CSS一样的,创建一个css,通过link引入

  1. body {
  2. color: red;
  3. }
  1. <link rel="stylesheet" type="text/css" href="./css/index.css" />

也可以在script中直接引入css(注意:一定要添加type="module"

  1. <script type="module">
  2. import './css/index.css'
  3. </script>

运行项目

通过运行 yarn start运行项目,会自动打开浏览器访问,会在node_modules下生成.cache/snowpack目录:
Snipaste_2022-04-11_16-23-26.png

构建项目

通过运行 yarn build构建项目,会生成一个build目录:
Snipaste_2022-04-08_14-49-52.png
直接开启本地服务即可运行。

✨ 使用snowpack脚手架创建项目

针对一些特定项目(比如Vue、React),snowpack提供对应的脚手架创建项目。

  • 官方文档:Create Snowpack App (CSA)
    1. npx create-snowpack-app new-dir --template @snowpack/app-template-NAME [--use-yarn | --use-pnpm | --no-install | --no-git]

官方模板:

创建空模板项目

使用@snowpack/app-template-minimal创建一个空模板项目

  1. npx create-snowpack-app snowpack-test --template @snowpack/app-template-minimal --use-yarn
  2. cd snowpack-test
  3. npm run start

查看package.json,这样创建出的是一个最小化的snowpack应用程序,并没有用到其他依赖。

  1. {
  2. "scripts": {
  3. "start": "snowpack dev",
  4. "build": "snowpack build"
  5. },
  6. "devDependencies": {
  7. "snowpack": "^3.3.7"
  8. }
  9. }

创建React项目

通过模板创建React项目

  1. npx create-snowpack-app snowpack-react-test --template @snowpack/app-template-react --use-yarn
  2. cd snowpack-react-test
  3. yarn start

Snipaste_2022-04-11_09-56-34.png
查看package.json,可以看到使用了以下依赖:

  1. {
  2. "scripts": {
  3. "start": "snowpack dev",
  4. "build": "snowpack build",
  5. "test": "web-test-runner \"src/**/*.test.jsx\"",
  6. "format": "prettier --write \"src/**/*.{js,jsx}\"",
  7. "lint": "prettier --check \"src/**/*.{js,jsx}\""
  8. },
  9. "dependencies": {
  10. "react": "^17.0.2",
  11. "react-dom": "^17.0.2"
  12. },
  13. "devDependencies": {
  14. "@snowpack/plugin-dotenv": "^2.1.0",
  15. "@snowpack/plugin-react-refresh": "^2.5.0",
  16. "@snowpack/web-test-runner-plugin": "^0.2.2",
  17. "@testing-library/react": "^11.2.6",
  18. "@web/test-runner": "^0.13.3",
  19. "chai": "^4.3.4",
  20. "prettier": "^2.2.1",
  21. "snowpack": "^3.3.7"
  22. }
  23. }

通过空模板创建React项目

  1. npx create-snowpack-app snowpack-react-test --template @snowpack/app-template-minimal --use-yarn
  2. cd snowpack-react-test
  3. npm run start

这样创建出的是一个最小化的snowpack应用程序,并没有用到其他依赖。

我们得手动添加依赖:

  1. yarn add react react-dom

然后将index.js重命名为index.jsx,内容替换为:

  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. ReactDOM.render(<div>"HELLO REACT"</div>, document.getElementById('root'));

index.html中引入的仍然是index.js

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1" />
  6. <meta name="description" content="Starter Snowpack App" />
  7. <title>Starter Snowpack App</title>
  8. </head>
  9. <body>
  10. <div id="root"></div>
  11. <script type="module" src="/index.js"></script>
  12. </body>
  13. </html>

重新运行 yarn start,即可看到效果。

使用@snowpack/plugin-react-refresh

@snowpack/plugin-react-refresh用于增强snowpack对于React快速刷新。

安装:

  1. yarn add -D @snowpack/plugin-react-refresh

配置:

  1. module.exports = {
  2. plugins: ['@snowpack/plugin-react-refresh'],
  3. ...
  4. };

修改index.jsx

  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import App from './App.jsx';
  4. import './index.css';
  5. ReactDOM.render(
  6. <React.StrictMode>
  7. <App />
  8. </React.StrictMode>,
  9. document.getElementById('root'),
  10. );
  11. // Hot Module Replacement (HMR) - Remove this snippet to remove HMR.
  12. // Learn more: https://www.snowpack.dev/concepts/hot-module-replacement
  13. if (import.meta.hot) {
  14. import.meta.hot.accept();
  15. }

其中App.jsx示例:

  1. import React, {useState, useEffect} from 'react';
  2. function App() {
  3. // Create the count state.
  4. const [count, setCount] = useState(0);
  5. // Update the count (+1 every second).
  6. useEffect(() => {
  7. const timer = setTimeout(() => setCount(count + 1), 1000);
  8. return () => clearTimeout(timer);
  9. }, [count, setCount]);
  10. // Return the App component.
  11. return (
  12. <div className="App">
  13. <header className="App-header">
  14. <p>
  15. Page has been open for <code>{count}</code> seconds.
  16. </p>
  17. </header>
  18. </div>
  19. );
  20. }
  21. export default App;

创建Vue项目

通过模板创建Vue项目

  1. npx create-snowpack-app snowpack-vue-test --template @snowpack/app-template-vue --use-yarn
  2. cd snowpack-vue-test
  3. yarn start

Snipaste_2022-04-11_09-56-48.png
查看package.json,可以看出其实是使用了@snowpack/plugin-vue插件解析Vue文件:

  1. {
  2. "scripts": {
  3. "start": "snowpack dev",
  4. "build": "snowpack build"
  5. },
  6. "dependencies": {
  7. "vue": "^3.0.11"
  8. },
  9. "devDependencies": {
  10. "@snowpack/plugin-dotenv": "^2.1.0",
  11. "@snowpack/plugin-vue": "^2.4.0",
  12. "snowpack": "^3.3.7"
  13. }
  14. }

通过空模板创建Vue项目

  1. npx create-snowpack-app snowpack-vue-test --template @snowpack/app-template-minimal --use-yarn
  2. cd snowpack-vue-test
  3. npm run start

这样创建出的是一个最小化的snowpack应用程序,并没有用到其他依赖。

我们得手动添加依赖:

  1. yarn add vue@3.0.11
  2. yarn add -D @snowpack/plugin-vue

然后在snowpack.config.mjs中注册插件:

  1. export default {
  2. plugins: [
  3. /* ... */
  4. '@snowpack/plugin-vue',
  5. ],
  6. ...
  7. };

修改index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1" />
  6. <meta name="description" content="Starter Snowpack App" />
  7. <title>Starter Snowpack App</title>
  8. </head>
  9. <body>
  10. <div id="root"></div>
  11. <script type="module" src="/index.js"></script>
  12. </body>
  13. </html>

修改index.js

  1. import {createApp} from 'vue';
  2. import App from './App.vue';
  3. const app = createApp(App);
  4. app.mount('#app');

创建App.vue

  1. <script>
  2. export default {
  3. setup() {
  4. return {};
  5. },
  6. };
  7. </script>
  8. <template>
  9. <div>Welcome to my Vue app!</div>
  10. </template>

重新运行 yarn start,即可看到效果。

创建Svelte项目

通过模板创建Svelte项目

  1. npx create-snowpack-app snowpack-svelte-test --template @snowpack/app-template-svelte --use-yarn
  2. cd snowpack-svelte-test
  3. npm run start

Snipaste_2022-04-11_11-21-53.png
查看package.json,可以看出其实是使用了@snowpack/plugin-svelte插件解析Svelte文件:

  1. {
  2. "scripts": {
  3. "start": "snowpack dev",
  4. "build": "snowpack build",
  5. "test": "web-test-runner \"src/**/*.test.js\""
  6. },
  7. "dependencies": {
  8. "svelte": "^3.37.0"
  9. },
  10. "devDependencies": {
  11. "@snowpack/plugin-dotenv": "^2.1.0",
  12. "@snowpack/plugin-svelte": "^3.6.1",
  13. "@snowpack/web-test-runner-plugin": "^0.2.2",
  14. "@testing-library/svelte": "^3.0.3",
  15. "@web/test-runner": "^0.13.3",
  16. "chai": "^4.3.4",
  17. "snowpack": "^3.3.7"
  18. }
  19. }

通过空模板创建Svelte项目

  1. npx create-snowpack-app snowpack-svelte-test --template @snowpack/app-template-minimal --use-yarn
  2. cd snowpack-svelte-test
  3. npm run start

这样创建出的是一个最小化的snowpack应用程序,并没有用到其他依赖。

我们得手动添加依赖:

  1. yarn add svelte
  2. yarn add -D @snowpack/plugin-svelte

然后在snowpack.config.mjs中注册插件:

  1. export default {
  2. plugins: [
  3. /* ... */
  4. '@snowpack/plugin-svelte',
  5. ],
  6. ...
  7. };

配置完后,创建一个svelte文件:

  1. <script>
  2. /* component logic will go here */
  3. </script>
  4. <style>
  5. /* css will go here */
  6. </style>
  7. <div class="App">
  8. <header class="App-header">
  9. <a class="App-link" href="https://svelte.dev" target="_blank" rel="noopener noreferrer">
  10. Learn Svelte
  11. </a>
  12. </header>
  13. </div>

再在index.js中引入:

  1. import App from "./App.svelte";
  2. let app = new App({
  3. target: document.body,
  4. });
  5. export default app;

重新运行 yarn start,即可看到效果。

✨ snowpack工程化

引入less

使用snowpack-plugin-less以增加对less的支持。

安装依赖:

  1. yarn add -D snowpack-plugin-less less

snowpack.config.json中添加配置:

  1. {
  2. "plugins": [
  3. "snowpack-plugin-less"
  4. ]
  5. }

比如创建一个 index.less文件:

  1. .a {
  2. .b {
  3. color: red;
  4. }
  5. }

index.js中引入less文件:

  1. import './index.less'

index.html中引入js:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <script type="module" src="./index.js"></script>
  5. </head>
  6. <body>
  7. <div class="a">
  8. <div class="b">hello</div>
  9. </div>
  10. </body>
  11. </html>

也可以在html的script中直接引入less文件(注意:一定要添加type="module"):

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <script type="module">
  5. import './index.less'
  6. </script>
  7. </head>
  8. <body>
  9. <div class="a">
  10. <div class="b">hello</div>
  11. </div>
  12. </body>
  13. </html>

引入scss/sass

使用@snowpack/plugin-sass插件以增加对sass的支持。

安装依赖:

  1. yarn add -D @snowpack/plugin-sass sass

snowpack.config.json中添加配置:

  1. {
  2. "plugins": [
  3. [
  4. '@snowpack/plugin-sass',
  5. {
  6. /* plugin options */
  7. },
  8. ],
  9. ]
  10. }

比如创建一个 index.scss文件:

  1. .a {
  2. .b {
  3. color: red;
  4. }
  5. }

index.js中引入scss文件:

  1. import './index.scss'

index.html中引入js:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <script type="module" src="./index.js"></script>
  5. </head>
  6. <body>
  7. <div class="a">
  8. <div class="b">hello</div>
  9. </div>
  10. </body>
  11. </html>

也可以在html的script中直接引入scss(注意:一定要添加type="module"):

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <script type="module">
  5. import './index.scss'
  6. </script>
  7. </head>
  8. <body>
  9. <div class="a">
  10. <div class="b">hello</div>
  11. </div>
  12. </body>
  13. </html>

引入pug

使用snowpack-plugin-pug插件以增加对sass的支持。

安装依赖:

  1. yarn add -D @marlonmarcello/snowpack-plugin-pug pug

snowpack.config.json中添加配置:

  1. export default {
  2. plugins: [
  3. ['@marlonmarcello/snowpack-plugin-pug',
  4. {
  5. "data": {
  6. "meta": {
  7. "title": "My website"
  8. }
  9. }
  10. }
  11. ]
  12. ]
  13. }

index.html重命名为 index.pug,修改内容(注意title的传值):

  1. doctype html
  2. html(lang="en")
  3. head
  4. meta(charset="UTF-8")
  5. meta(name="viewport", content="width=device-width, initial-scale=1.0")
  6. title!=meta.title
  7. body
  8. div Hello

通过地址访问 http://localhost:8080/index.html,注意必须刚上文件名(index.html),暂时不知道解决方案。

如果要在pug中引入js或其他文件,可以通过以下写法引入:

  1. doctype html
  2. html(lang="en")
  3. head
  4. meta(charset="UTF-8")
  5. meta(name="viewport", content="width=device-width, initial-scale=1.0")
  6. title!=meta.title
  7. script(type='module' src="./index.js")
  8. script(type="module").
  9. import './index.scss'

✨ snowpack配置

snowpack配置文件位于snowpack.config.mjs

配置目标路径

mount节点配置生成目录的映射,格式为:

  1. mount: {
  2. [path: string]: string | {url: string, resolve: boolean, static: boolean, dot: boolean}
  3. }

其中key为源路径;value可以为字符串,也可以为对象,为目标路径。
当value为对象时,支持以下选项:

  • mount.url | string | required : The URL to mount to, matching the string in the simple form above.
  • mount.static | boolean | optional | Default: false : If true, don’t build files in this directory. Copy and serve them directly from disk to the browser.
  • mount.resolve | boolean | optional | Default: true: If false, don’t resolve JS & CSS imports in your JS, CSS, and HTML files. Instead send every import to the browser, as written.
  • mount.dot | boolean | optional | Default: false: If true, include dotfiles (ex: .htaccess) in the final build.

示例:

  1. export default {
  2. mount: {
  3. // directory name: 'build directory'
  4. public: '/',
  5. src: '/dist',
  6. },
  7. };

或者:

  1. export default {
  2. mount: {
  3. public: {url: '/', static: true},
  4. src: {url: '/dist'},
  5. },
  6. }

图解如下:
folder-structure.webp
配置后,在相应引入文件的地方也得修改路径:

  1. index.html --> public/index.html
  2. index.css --> public/index.css
  3. index.js --> src/index.js
  1. <body>
  2. <h1>Welcome to Snowpack!</h1>
  3. - <script type="module" src="/index.js"></script>
  4. + <script type="module" src="/dist/index.js"></script>
  5. </body>

别名

使用alias选项可以配置别名,举例:

  1. export default {
  2. alias: {
  3. '@components': './src/components',
  4. '@': './src',
  5. }
  6. };

比如在Vue项目中使用:

  1. <template>
  2. <div class="App">
  3. <AAAVue></AAAVue>
  4. </div>
  5. </template>
  6. <script>
  7. import AAAVue from '@components/AAA.vue';
  8. export default {
  9. components: { AAAVue }
  10. }
  11. </script>

除了路径别名外,node_modules里面的包也可通过这种方式设置别名:

  1. export default {
  2. alias: {
  3. lodash: 'lodash-es',
  4. react: 'preact/compat',
  5. },
  6. };

插件

插件配置有两种格式,单字符串或数组形式:

  1. export default {
  2. plugins: [
  3. // Simple format: no options needed
  4. 'plugin-1',
  5. // Expanded format: allows you to pass options to the plugin
  6. ['plugin-2', {'plugin-option': false}],
  7. ];
  8. }

如果是数组形式,第0项为插件名称,第1项为插件选项

可以在这个页面查询需要的插件:

✨ snowpack原理

snowpack 的最初版核心目标就是不再打包业务代码,而是直接使用浏览器原生的 JavaScript Module 能力。

所以从它的处理流程上来看,对业务代码的模块,基本只需要把 ESM 发布(拷贝)到发布目录,再将模块导入路径从源码路径换为发布路径即可。
而对 node_modules 则通过遍历 package.json 中的依赖,按该依赖列表为粒度将 node_modules 中的依赖打包。以 node_modules 中每个包的入口作为打包 entry,使用 rollup 生成对应的 ESM 模块文件,放到 web_modules 目录中,最后替换源码的 import 路径,是得可以通过原生 JavaScript Module 来加载 node_modules 中的包。

  1. node_modules/react/**/* -> http://localhost:3000/web_modules/react.js
  2. node_modules/react-dom/**/* -> http://localhost:3000/web_modules/react-dom.js

对比源码和生成后的代码对比大概长这样:

  1. - import { createElement, Component } from "preact";
  2. - import htm from "htm";
  3. + import { createElement, Component } from "/web_modules/preact.js";
  4. + import htm from "/web_modules/htm.js";

✨ snowpack命令行

查看帮助

通过执行npx snowpack --help可以看到所有的snowpack命令:

  1. $ npx snowpack --help
  2. [18:06:52] [snowpack] snowpack - A faster build system for the modern web.
  3. Snowpack is best configured via config file.
  4. But, most configuration can also be passed via CLI flags.
  5. 📖 https://www.snowpack.dev/reference/configuration
  6. Commands:
  7. snowpack init Create a new project config file.
  8. snowpack prepare Prepare your project for development (optional).
  9. snowpack dev Develop your project locally.
  10. snowpack build Build your project for production.
  11. snowpack add [package] Add a package to your project.
  12. snowpack rm [package] Remove a package from your project.
  13. Flags:
  14. --config [path] Set the location of your project config file.
  15. --help Show this help message.
  16. --version Show the current version.
  17. --reload Clear the local cache (useful for troubleshooting).
  18. --cache-dir-path Specify a custom cache directory.
  19. --verbose Enable verbose log messages.
  20. --quiet Enable minimal log messages.

指定包管理工具

如果不想要使用npm作为包管理工具,可以添加以下参数指定其他包管理工具:

  • --use-yarn
  • --use-pnpm

✨ 参考资料