学了Vite再来看看Snowpack吧

什么是 Snowpack?

看下官网的介绍:

Snowpack is a lightning-fast frontend build tool, designed for the modern web. It is an alternative to heavier, more complex bundlers like webpack or Parcel in your development workflow. Snowpack leverages JavaScript's native module system (known as ESM) to avoid unnecessary work and stay fast no matter how big your project grows.
Once you try it, it's impossible to go back to anything else.


Snowpack 是一款闪电般快速的前端构建工具,专为现代 Web 设计。 它是开发工作流程中更重、更复杂的打包程序(如 webpack 或 Parcel)的替代品。Snowpack 利用 JavaScript 的原生模块系统(称为 ESM)来避免不必要的工作,无论您的项目有多大,都能保持快速。
一旦你尝试过,就不可能回到其他任何事情上。

从这段介绍可以看出,Snowpack 的特点就是——快!

本文目标

  • 基于 Snowpack 搭建 react 项目,包括路由、UI等功能;
  • 项目的扩展功能,如 typescript、less、eslint、stylelint、prettier;
  • 围绕项目的相关配置内容,如环境变量、路径别名、打包路径等。

创建 Snowpack 项目

初始化

通过以下命令创建 Snowpack 项目。

npx create-snowpack-app react-snowpack --template @snowpack/app-template-minimal

创建完成后可以通过以下两个命令进入该目录,进行启动。

cd react-snowpack
npm run start

运行完后,你就可以看到 Snowpack 启动的项目了。


打开这个项目你会发现里面空空如也,别急,接下来我们为它添加上 React。

React 的第一个页面

运行以下命令进行安装。

npm install react react-dom react-router-dom --save

将 React logo 图片放置在 src/assets/images 下。

在根目录下创建文件夹 srcsrc 内创建 App.jsx 文件,这将是我们的第一个页面,App.jsx 的内容如下。

import React, { useState, useEffect } from "react";
import logo from "./assets/images/logo.png";
import "./App.css";

function App() {
        const [count, setCount] = useState(0);
  
    useEffect(() => {
            const timer = setTimeout(() => setCount(count + 1), 1000);
            return () => clearTimeout(timer);
    }, [count, setCount]);
  
    return (
            <div className="App">
                <header className="App-header">
                <img src={logo} className="App-logo" alt="logo" />
                        <h1>Hello snowpack!</h1>
                      <p>
                        Page has been open for <code>{count}</code> seconds.
                      </p>
                </header>
            </div>
  );
}

export default App;

创建 src/App.css ,并添加以下内容:

.App {
    text-align: center;
}

.App p {
    margin: 0.4rem;
}

.App-logo {
    height: 40vmin;
}

@media (prefers-reduced-motion: no-preference) {
    .App-logo {
        animation: App-logo-spin infinite 20s linear;
    }
}

.App-header {
    background-color: #282c34;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    font-size: calc(10px + 2vmin);
    color: white;
}

@keyframes App-logo-spin {
    from {
        transform: rotate(0deg);
    }
    to {
        transform: rotate(360deg);
    }
}

打开 index.html 文件,添加一个空的 <div> 标签作为标记你想要用 React 显示内容的位置。

<div id="root"></div>

删除根目录下的 index.js 文件,创建 index.jsx,用来渲染 React Dom。

import React from "react";
import ReactDOM from "react-dom";
import APP from "./src/App";
ReactDOM.render(
    <React.StrictMode>
            <APP />
    </React.StrictMode>,
    document.getElementById("root")
);

等待页面自动刷新后,就可以看到我们创建的第一个页面了。


添加路由

在单页面项目中路由是必不可少的,下面我们来添加路由。
分别创建 src/pages/hello/index.jsx 文件和 src/pages/snowpack/index.jsx 文件,作为路由跳转的测试页面。内容如下:

// hello/index.jsx
import React from "react";

function Hello() {
    return <div className="App">I'm Hello, How are u?</div>;
}

export default Hello;
// snowpack/index.jsx
import React from "react";

function Snowpack() {
    return <div className="App">I'm Snowpack, I'm fine thank you, and you?</div>;
}

export default Snowpack;

App.jsxAPP.css 移到 src/home 下,并更改文件名, App.jsx -> index.jsxAPP.css -> index.css ,更改 index.jsx 的组件名为 Home
src 下重新创建 APP.jsx 放置路由内容。

import React from "react";
import { HashRouter, Switch, Route } from "react-router-dom";
import Index from "./pages/home/index.jsx";
import Hello from "./pages/hello/index.jsx";
import Snowpack from "./pages/snowpack/index.jsx";

function App() {
    return (
            <div className="index">
            <HashRouter>
                <Switch>
                    <Route exact path="/" component={Index} />
                    <Route exact path="/hello" component={Hello} />
                    <Route exact path="/snowpack" component={Snowpack} />
                </Switch>
            </HashRouter>
            </div>
    );
}

pages/home/index 下添加路由跳转。

import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import logo from '../../assets/images/logo.svg';
import './index.css';

function Home() {
    const [count, setCount] = useState(0);

    useEffect(() => {
        const timer = setTimeout(() => setCount(count + 1), 1000);
        return () => clearTimeout(timer);
    }, [count, setCount]);

    return (
        <div className="App">
            <header className="App-header">
                <nav>
                    <ul>
                        <li>
                            <Link to="/hello">hello</Link>
                        </li>
                        <li>
                            <Link to="/snowpack">snowpack</Link>
                        </li>
                    </ul>
                </nav>
                <img src={logo} className="App-logo" alt="logo" />
                <h1>Hello snowpack!</h1>
                <p>
                    Page has been open for <code>{count}</code> seconds.
                </p>
            </header>
        </div>
    );
}

export default Home;

现在在页面中出现了跳转链接,点击链接就可以实现跳转了!


跳转效果:


Antd

安装以下包,@ant-design/iconsantd icon 依赖库, dayjsantd 内部组件依赖库。

npm install antd @ant-design/icons dayjs --save 

然后在 App.jsx 中引入 import "antd/dist/antd.css"; 所需css,就可以直接使用相关组件。

工程化配置

基本的项目建好了,下面我们开始工程化相关配置。

Less

安装 less

npm install less --save-dev

安装 snowpack-plugin-less,使 snowpack 可以对 less 进行识别并转换为 css

npm install snowpack-plugin-less --save-dev

然后在 snowpack.config.mjs 中添加 snowpack-plugin-less 插件。

// snowpack.config.mjs
export default {
    plugins: ['snowpack-plugin-less']
}

Eslint

安装 eslint

npm install eslint --save-dev

安装 eslint 包后运行 eslint --init ,下图为我的安装选项。


安装完成后会在项目中自动创建,.eslintrc文件,使用 hooks 的同学还需要额外安装 eslint-plugin-react-hooks 包,以便 eslint 可以执行 hooks 的相关规则。
下面是推荐配置:

module.exports = {
    extends: [
        'eslint:recommended',
        'plugin:react/recommended',
        'plugin:react-hooks/recommended',
        'standard',
        'prettier',
    ],
    env: {
        browser: true,
        commonjs: true,
        es6: true
    },
    parser: '@typescript-eslint/parser',
    parserOptions: {
        ecmaFeatures: {
            jsx: true,
            modules: true
        },
        sourceType: 'module',
        ecmaVersion: 6
    },
    plugins: ['react', '@typescript-eslint'],
    settings: {
        'import/ignore': ['node_modules'],
        react: {
            version: 'latest'
        }
    },
    rules: {
        quotes: [2, 'single'],
        'no-console': 0,
        'no-debugger': 1,
        'no-var': 1,
        semi: ['error', 'always'],
        'no-irregular-whitespace': 0,
        'no-trailing-spaces': 1,
        'eol-last': 0,
        'no-unused-vars': [
            1,
            {
                vars: 'all',
                args: 'after-used'
            }
        ],
        'no-prototype-builtins': 0,
        'no-case-declarations': 0,
        'no-underscore-dangle': 0,
        'no-alert': 2,
        'no-lone-blocks': 0,
        'no-class-assign': 2,
        'no-cond-assign': 2,
        'no-const-assign': 2,
        'no-delete-var': 2,
        'no-dupe-keys': 2,
        'use-isnan': 2,
        'no-duplicate-case': 2,
        'no-dupe-args': 2,
        'no-empty': 2,
        'no-func-assign': 2,
        'no-invalid-this': 0,
        'no-redeclare': 2,
        'no-spaced-func': 2,
        'no-this-before-super': 0,
        'no-undef': 2,
        'no-return-assign': 0,
        'no-script-url': 2,
        'no-use-before-define': 0,
        'no-extra-boolean-cast': 0,
        'no-unreachable': 1,
        'comma-dangle': 2,
        'no-mixed-spaces-and-tabs': 2,
        'prefer-arrow-callback': 0,
        'arrow-parens': 0,
        'arrow-spacing': 0,
        camelcase: 0,
        'jsx-quotes': [1, 'prefer-double'],
        'react/display-name': 0,
        'react/forbid-prop-types': [
            2,
            {
                forbid: ['any']
            }
        ],
        'react/jsx-boolean-value': 0,
        'react/jsx-closing-bracket-location': 1,
        'react/jsx-curly-spacing': [
            2,
            {
                when: 'never',
                children: true
            }
        ],
        'react/jsx-indent': ['error', 4],
        'react/jsx-key': 2,
        'react/jsx-no-bind': 0,
        'react/jsx-no-duplicate-props': 2,
        'react/jsx-no-literals': 0,
        'react/jsx-no-undef': 1,
        'react/jsx-pascal-case': 0,
        'react/jsx-sort-props': 0,
        'react/jsx-uses-react': 1,
        'react/jsx-uses-vars': 2,
        'react/no-danger': 0,
        'react/no-did-mount-set-state': 0,
        'react/no-did-update-set-state': 0,
        'react/no-direct-mutation-state': 2,
        'react/no-multi-comp': 0,
        'react/no-set-state': 0,
        'react/no-unknown-property': 2,
        'react/prefer-es6-class': 2,
        'react/prop-types': 0,
        'react/react-in-jsx-scope': 0,
        'react/self-closing-comp': 0,
        'react/sort-comp': 0,
        'react/no-array-index-key': 0,
        'react/no-deprecated': 1,
        'react/jsx-equals-spacing': 2
    }
};

配置成功后很多文件已经变红了,是不是有些同学已经犯强迫症,想要手动修改这些问题了呢。不要急,后面我们会安装 prettier ,实现一键整理代码格式。

Stylelint

有了 eslint 自然 stylelint 也不能少,eslint 帮我们检测js的代码格式,stylelint 则帮我们检测 css 的代码格式。
首先安装 stylelint 相关包,然后需要在项目中创建 stylelint.config.js 用来存放 stylelint 相关规则。

npm install stylelint stylelint-config-standard --save-dev 
echo {}> stylelint.config.js

下面是推荐配置:

module.exports = {
    extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
    ignoreFiles: [
        '**/*.ts',
        '**/*.tsx',
        '**/*.png',
        '**/*.jpg',
        '**/*.jpeg',
        '**/*.gif',
        '**/*.mp3',
        '**/*.json'
    ],
    rules: {
        'at-rule-no-unknown': [
            true,
            {
                ignoreAtRules: ['extends', 'ignores']
            }
        ],
        indentation: 4,
        'number-leading-zero': null,
        'unit-allowed-list': ['em', 'rem', 's', 'px', 'deg', 'all', 'vh', 'vw', '%'],
        'no-eol-whitespace': [
            true,
            {
                ignore: 'empty-lines'
            }
        ],
        'declaration-block-trailing-semicolon': 'always',
        'selector-pseudo-class-no-unknown': [
            true,
            {
                ignorePseudoClasses: ['global']
            }
        ],
        'block-closing-brace-newline-after': 'always',
        'declaration-block-semicolon-newline-after': 'always',
        'no-descending-specificity': null,
        'selector-list-comma-newline-after': 'always',
        'selector-pseudo-element-colon-notation': 'single'
    }
};

Prettier

接下来就到了每个前端开发必备的 prettier,有了它再也不用手动整理格式,它可以帮助我们一键整理代码格式。
安装 prettier

npm install prettier  --save-dev

因为项目中还用到了 eslint,所以需要安装 eslint-config-prettier ,使 eslint可以和 prettier相互配合,它可以关闭与 prettier 冲突的 eslint 规则。
然后通过 echo {}> prettier.config.js 创建 prettier 配置文件。
下面是推荐配置:

module.exports = {
    // 一行最多 100 字符
    printWidth: 100,
    // 使用 4 个空格缩进
    tabWidth: 4,
    // 不使用缩进符,而使用空格
    useTabs: false,
    // 行尾需要有分号
    semi: true,
    // 使用单引号
    singleQuote: true,
    // 对象的 key 仅在必要时用引号
    quoteProps: 'as-needed',
    // jsx 不使用单引号,而使用双引号
    jsxSingleQuote: false,
    // 末尾不需要逗号
    trailingComma: 'none',
    // 大括号内的首尾需要空格
    bracketSpacing: true,
    // jsx 标签的反尖括号需要换行
    jsxBracketSameLine: false,
    // 箭头函数,只有一个参数的时候,也需要括号
    arrowParens: 'avoid',
    // 每个文件格式化的范围是文件的全部内容
    rangeStart: 0,
    rangeEnd: Infinity,
    // 不需要写文件开头的 @prettier
    requirePragma: false,
    // 不需要自动在文件开头插入 @prettier
    insertPragma: false,
    // 使用默认的折行标准
    proseWrap: 'preserve',
    // 根据显示样式决定 html 要不要折行
    htmlWhitespaceSensitivity: 'css',
    // 换行符使用 lf
    endOfLine: 'lf'
};

然后在命令行中运行以下命令就可以将全部文件进行格式化。

npx prettier --write .

如果希望在 vscode 中通过保存一键格式化,需要安装 Prettier - Code formatter 插件,根据教程对 vscode 配置进行修改。

Typescript

通过以下命令安装 typescript 以及 reactreact-dom 的类型定义。

npm install typescript --save-dev
npm install @types/react --save-dev
npm install @types/react-dom --save-dev
npm install @typescript-eslint/eslint-plugin --save-dev
npm install @typescript-eslint/parser --save-dev

如果需要在编译时进行打包检测,还需要安装以下包。

npm install @snowpack/plugin-typescript --save-dev

并在 snowpack.config.mjsplugins 中进行配置:

// snowpack.config.mjs
export default {
    plugins: ['@snowpack/plugin-typescript']
}

通过 echo {}> tsconfig.json 创建 tsconfig.json 并进行配置。
以下为推荐配置:

{
    "compilerOptions": {
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "target": "ESNext",
        "allowSyntheticDefaultImports": true,
        "strict": true,
        "forceConsistentCasingInFileNames": true,
        "allowJs": true,
        "outDir": "./dist/",
        "esModuleInterop": true,
        "noImplicitAny": false,
        "sourceMap": true,
        "module": "esnext",
        "moduleResolution": "node",
        "isolatedModules": true,
        "importHelpers": true,
        "lib": ["esnext", "dom", "dom.iterable"],
        "skipLibCheck": false,
        "jsx": "react",
        "baseUrl": "./src",
        "paths": {
              "@src/*": ["*"],
              "@assets/*": ["assets/*"],
              "@components/*": ["components/*"],
              "@pages/*": ["pages/*"],
              "@utils/*": ["utils/*"],
              "@servers/*": ["servers/*"],
              "@actions/*": ["actions/*"],
              "@config": ["config"],
              "@routeConfig": ["routeConfig"],
              "@request": ["request"]
          }
    },
    "include": ["./src/**/*", "./declaration.d.ts"],
    "exclude": ["node_modules"]
}

tsconfig.jsonpaths 为 ts 所支持的路径别名,除了在 tsconfig.json 中需要设置,还需要在 snowpack.config.mjs 中进行配置,以便于让 snowpack 打包时可以转换成对应路径,关于项目配置请看 [snowpack.config.mjs 相关配置-路径别名配置] 相关章节。

pre-commit

安装 huskylist-staged

npm install husky -save-dev
npm install lint-staged -save-dev

package.json 中添加一下配置。

"scripts": {
                ...,
        "lint:jsx": "eslint --ext .jsx,.js src",
        "lint:css": "stylelint --aei .less .css src",
        "precommit": "lint-staged",
        "precommit-msg": "echo 'Pre-commit checks...' && exit 0"
},
"husky": {
        "hooks": {
                "pre-commit": "npm run lint-staged"
        }
},
"lint-staged": {
    "*.{js,jsx,ts,tsx}": [
        "eslint --fix",
        "prettier --write"
    ],
    "*.{css,less}": [
        "stylelint --fix",
        "prettier --write"
    ]
}

snowpack.config.mjs 相关配置

打包路径配置

首先我们先整理下项目的公共资源文件,一般来说会把项目的 index.htmlindex.css 看做静态资源放置在 /public 目录下。如下图所示:


移动过后注意要修改 index.html 中 css 及 js 的引入路径。
按上面步骤操作完成后,项目的基本结构和内容就已经配置好了,现在我们在命令行运行 npm run build 看一下打包后的文件结构是怎样的。

打包结束后在项目根目录下生成了 build 文件夹,展开 build 可以看到,public 中的文件被打包至 build/public 文件中,但通常我们期望 index.html 能在 build 的根目录下。可以通过以下配置将 public 挂载到 build根目录下。

// snowpack.config.mjs
export default {
    mount: {
                public: '/',
        }
}

并且打包后的文件不只有我们的业务代码,还有项目的配置代码,如 package.jsonperttier.config.jsREADME.md 等。这些代码是我们不需要的,并且会占用少量空间,可以通过以下配置设置只打包 src 目录下的文件。

// snowpack.config.mjs
export default {
    mount: {
                src: '/src',
        }
}

打包输出目录配置

// snowpack.config.mjs
export default {
    out: 'dist'
};

路径别名配置

通过 alias 可配置路径缩写别名,及 node 包的引入别名。

// snowpack.config.mjs
export default {
    alias: {
        // Type 1: node 包 引入别名
        lodash: 'lodash-es',
        // Type 2: 本地文件引入别名
        '@src': './src',
        '@assets': './src/assets',
        '@components': './src/components',
        '@pages': './src/pages',
        '@utils': './src/utils/',
        '@servers': './src/servers',
        '@actions': './src/actions',
        '@config': './src/config.ts',
        '@routeConfig': './src/routeConfig.tsx',
        '@request': './src/request.ts'
    },
};

打包环境区分

通常我们在打包时,希望通过打包命令区分包所对应环境。
首先需要在 package.jsonscripts 配置中,给对应 script 命令添加环境所对应值。

"scripts": {
    "start": "NODE_ENV=development snowpack dev",
    "build:test": "NODE_ENV=test snowpack build",
    "build:prod": "NODE_ENV=production snowpack build",
},

如果需要在 snowpack.config.mjs 中获取环境,通过 process.env.NODE_ENV 可直接获取到,如下:

// snowpack.config.mjs
export default {
    plugins: (() => {
        const plugin = ['snowpack-plugin-less'];
        if (process.env.NODE_ENV === 'development') {
            plugin.push('@snowpack/plugin-typescript');
        }
        return plugin;
        })(),
}

在js文件中则需要通过 import.meta.env 中的 NODE_ENV 获取,如下:

const { NODE_ENV } = import.meta.env;

设置全局变量

有时我们还需要在设置一些内置的全局变量,供 html 及 js 文件使用,在 snowpack.config.mjsenv 配置中设置即可,如下:

// snowpack.config.mjs
export default {
    env: {
        ENVIRONMENT: 'test'
    },
}

通用在js文件中需要通过 import.meta.env 中获取:

const { ENVIRONMENT } = import.meta.env;

在 HTML 中则可以通过 %ENVIRONMENT% 直接使用:

<script>
    console.log('%ENVIRONMENT%');
</script>

在 devtools 中可以看到该部分内容变成了下面这样。


问题&注意事项

  1. snowpack 运行时页面报错 Uncaught ReferenceError: require is not defined.

报错原因是由于 snowpack 只支持 ESM 标准代码,不能解析 commonjs。

  1. snowpack 无法解析 cnpm 安装的包,所以在安装包后启动报错,或者发现包不生效,可以删除 node_modules,用 npmyarn 安装。

最后

经过这些一个基本的项目框架就基本完成了,小伙伴们快去动手实践下吧,有问题欢迎在评论区探讨。

  1. npm start 时报错
[18:30:15] [esinstall:xmlhttprequest-ssl] /Users/huxiaomiao/pivos/react-snowpack3/node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js
   Module "fs" (Node.js built-in) is not available in the browser. Run Snowpack with --polyfill-node to fix.
[18:30:15] [esinstall:xmlhttprequest-ssl] /Users/huxiaomiao/pivos/react-snowpack3/node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js
   Module "url" (Node.js built-in) is not available in the browser. Run Snowpack with --polyfill-node to fix.
[18:30:15] [esinstall:xmlhttprequest-ssl] /Users/huxiaomiao/pivos/react-snowpack3/node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js
   Module "https" (Node.js built-in) is not available in the browser. Run Snowpack with --polyfill-node to fix.
[18:30:15] [esinstall:xmlhttprequest-ssl] /Users/huxiaomiao/pivos/react-snowpack3/node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js
   Module "child_process" (Node.js built-in) is not available in the browser. Run Snowpack with --polyfill-node to fix.
[18:30:15] [esinstall:xmlhttprequest-ssl] /Users/huxiaomiao/pivos/react-snowpack3/node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js
   Module "url" (Node.js built-in) is not available in the browser. Run Snowpack with --polyfill-node to fix.
[18:30:15] [esinstall:xmlhttprequest-ssl] /Users/huxiaomiao/pivos/react-snowpack3/node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js
   Module "fs" (Node.js built-in) is not available in the browser. Run Snowpack with --polyfill-node to fix.
[18:30:15] [esinstall:xmlhttprequest-ssl] /Users/huxiaomiao/pivos/react-snowpack3/node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js
   Module "http" (Node.js built-in) is not available in the browser. Run Snowpack with --polyfill-node to fix.
[18:30:15] [esinstall:xmlhttprequest-ssl] /Users/huxiaomiao/pivos/react-snowpack3/node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js
   Module "http" (Node.js built-in) is not available in the browser. Run Snowpack with --polyfill-node to fix.
[18:30:15] [esinstall:xmlhttprequest-ssl] /Users/huxiaomiao/pivos/react-snowpack3/node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js
   Module "https" (Node.js built-in) is not available in the browser. Run Snowpack with --polyfill-node to fix.
[18:30:15] [esinstall:xmlhttprequest-ssl] /Users/huxiaomiao/pivos/react-snowpack3/node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js
   Module "child_process" (Node.js built-in) is not available in the browser. Run Snowpack with --polyfill-node to fix.
[18:30:15] [esinstall:xmlhttprequest-ssl] url?commonjs-external
   Module "url" (Node.js built-in) is not available in the browser. Run Snowpack with --polyfill-node to fix.
[18:30:15] [esinstall:xmlhttprequest-ssl] https?commonjs-external
   Module "https" (Node.js built-in) is not available in the browser. Run Snowpack with --polyfill-node to fix.
[18:30:15] [esinstall:xmlhttprequest-ssl] http?commonjs-external
   Module "http" (Node.js built-in) is not available in the browser. Run Snowpack with --polyfill-node to fix.
[18:30:15] [esinstall:xmlhttprequest-ssl] fs?commonjs-external
   Module "fs" (Node.js built-in) is not available in the browser. Run Snowpack with --polyfill-node to fix.
[18:30:15] [esinstall:xmlhttprequest-ssl] child_process?commonjs-external
   Module "child_process" (Node.js built-in) is not available in the browser. Run Snowpack with --polyfill-node to fix.
[18:30:15] [snowpack] Install failed for xmlhttprequest-ssl.

解决方式:

https://www.snowpack.dev/reference/configuration#packageoptionspolyfillnode

最后

经过这些一个基本的项目框架就基本完成了,小伙伴们快去动手实践下吧,有问题欢迎在评论区探讨。
下面为我搭建的简单项目框架,想体验的朋友们可以直接 clone 下来玩哦~
https://github.com/mogiihu/snowpack-react


本文首发于我的博客 mogii'blog 欢迎大家光临~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容