MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Svelte项目构建的最佳实践

2023-11-236.9k 阅读

项目初始化

  1. 使用官方脚手架 在开始一个 Svelte 项目时,最便捷的方式是使用官方提供的脚手架工具。Svelte 官方推荐使用 degit 来初始化项目。degit 是一个轻量级的工具,它可以快速克隆一个项目模板。

首先确保你已经安装了 Node.js,因为 degit 是基于 Node.js 运行的。如果没有安装,可以从 Node.js 官网 下载并安装。

安装 degit

npm install -g degit

安装完成后,使用以下命令初始化一个 Svelte 项目:

degit sveltejs/template my - svelte - app
cd my - svelte - app
npm install

上述命令中,degit sveltejs/template my - svelte - app 表示从 sveltejs/template 克隆项目模板,并命名为 my - svelte - app。进入项目目录后,npm install 用于安装项目所需的依赖。

  1. 自定义初始化 如果你对项目结构有特定的要求,也可以手动初始化一个 Svelte 项目。首先创建一个新的目录作为项目根目录:
mkdir my - custom - svelte - app
cd my - custom - svelte - app

然后初始化 package.json 文件:

npm init -y

接下来安装 Svelte 相关的依赖:

npm install svelte rollup - plugin - svelte rollup - plugin - commonjs rollup - plugin - resolve rollup - plugin - livereload rollup - plugin - serve @babel/core @babel/preset - env
  • svelte:Svelte 核心库,提供了构建用户界面的基础。
  • rollup - plugin - svelte:Rollup 插件,用于处理 Svelte 组件。
  • rollup - plugin - commonjs:将 CommonJS 模块转换为 ES6 模块,以便 Rollup 处理。
  • rollup - plugin - resolve:帮助 Rollup 解析模块路径。
  • rollup - plugin - livereload:实现实时重新加载,当代码更改时自动更新浏览器。
  • rollup - plugin - serve:启动一个简单的开发服务器。
  • @babel/core@babel/preset - env:用于将现代 JavaScript 代码转换为兼容旧浏览器的代码。

之后,需要手动创建项目的基本结构,例如 src 目录用于存放源代码,public 目录用于存放静态文件等。

目录结构设计

  1. 经典目录结构 一个典型的 Svelte 项目目录结构如下:
my - svelte - app
├── node_modules
├── public
│   ├── favicon.ico
│   ├── index.html
│   └── robots.txt
├── src
│   ├── App.svelte
│   ├── main.js
│   └── lib
│       └── some - utility.js
├── .gitignore
├── package - lock.json
├── package.json
└── rollup.config.js
  • node_modules:存放项目依赖的模块。
  • public:这个目录中的文件会直接复制到最终的构建输出目录。index.html 是项目的入口 HTML 文件,favicon.icorobots.txt 是常见的网站相关文件。
  • src:源代码目录。App.svelte 是项目的主组件,所有其他组件通常会被导入并使用在 App.svelte 中。main.js 是 JavaScript 入口文件,用于将 App.svelte 挂载到 DOM 上。lib 目录用于存放一些通用的工具函数或模块。
  • .gitignore:指定哪些文件或目录不被 Git 跟踪。
  • package - lock.json:记录 npm install 时安装的每个包的具体版本,确保团队成员安装的依赖版本一致。
  • package.json:包含项目的元数据和依赖信息,以及一些脚本命令。
  • rollup.config.js:Rollup 的配置文件,用于配置项目的打包过程。
  1. 按功能模块划分 对于较大型的项目,可以按功能模块来划分目录结构:
my - svelte - app
├── node_modules
├── public
│   ├── favicon.ico
│   ├── index.html
│   └── robots.txt
├── src
│   ├── main.js
│   ├── components
│       ├── auth
│           ├── Login.svelte
│           ├── Register.svelte
│       ├── dashboard
│           ├── Dashboard.svelte
│           ├── Widget.svelte
│   ├── stores
│       ├── auth - store.js
│       ├── user - store.js
│   ├── routes
│       ├── auth - routes.js
│       ├── dashboard - routes.js
│   ├── lib
│       └── api - client.js
├── .gitignore
├── package - lock.json
├── package.json
└── rollup.config.js

在这种结构中,components 目录按功能模块进一步细分,每个功能模块有自己的组件集合。stores 目录专门存放 Svelte 的存储(Stores),用于管理应用状态。routes 目录用于存放路由相关的代码,如果项目使用了路由功能。这种结构使得项目的可维护性和扩展性更好,不同功能模块之间的边界更加清晰。

组件设计与组织

  1. 单一职责原则 Svelte 组件应该遵循单一职责原则(SRP),即每个组件应该只有一个明确的职责。例如,一个 Button 组件应该只负责渲染按钮并处理按钮相关的交互逻辑,而不应该包含与按钮无关的业务逻辑。
<!-- Button.svelte -->
<script>
    let isClicked = false;
    const handleClick = () => {
        isClicked = true;
        console.log('Button clicked!');
    };
</script>

<button on:click={handleClick}>
    {isClicked? 'Clicked' : 'Click me'}
</button>

在这个 Button 组件中,它只关注按钮的点击状态和响应点击事件,没有其他额外的无关逻辑。

  1. 组件层次结构 合理组织组件的层次结构可以使项目更加清晰。通常,顶层组件(如 App.svelte)会包含一些中层组件,中层组件再包含底层组件。例如,在一个电商应用中,App.svelte 可能包含 HeaderMainContentFooter 组件。MainContent 组件可能又包含 ProductListCart 组件,而 ProductList 组件可能包含 ProductItem 组件。
<!-- App.svelte -->
<script>
    import Header from './components/Header.svelte';
    import MainContent from './components/MainContent.svelte';
    import Footer from './components/Footer.svelte';
</script>

<Header />
<MainContent />
<Footer />
<!-- MainContent.svelte -->
<script>
    import ProductList from './ProductList.svelte';
    import Cart from './Cart.svelte';
</script>

<ProductList />
<Cart />

通过这种层次结构,组件之间的关系一目了然,易于维护和扩展。

  1. 组件复用 Svelte 组件的复用非常方便。例如,一个 Card 组件可以在多个不同的地方使用。假设我们有一个 Card 组件用于展示产品信息:
<!-- Card.svelte -->
<script>
    let product;
    export let productData;
    $: product = productData;
</script>

<div class="card">
    <img src={product.image} alt={product.name} />
    <h3>{product.name}</h3>
    <p>{product.description}</p>
</div>

在其他组件中可以这样复用:

<!-- ProductList.svelte -->
<script>
    import Card from './Card.svelte';
    const products = [
        { name: 'Product 1', description: 'Description of product 1', image: 'product1.jpg' },
        { name: 'Product 2', description: 'Description of product 2', image: 'product2.jpg' }
    ];
</script>

{#each products as product}
    <Card productData={product} />
{/each}

这样通过传递不同的 productData,同一个 Card 组件可以展示不同的产品信息。

状态管理

  1. Svelte 存储(Stores)基础 Svelte 提供了简单而强大的状态管理机制——存储(Stores)。一个基本的存储可以通过 svelte/store 模块中的 writable 函数创建。
// counter - store.js
import { writable } from'svelte/store';

export const counter = writable(0);

在组件中使用这个存储:

<!-- Counter.svelte -->
<script>
    import { counter } from './counter - store.js';
    const increment = () => {
        counter.update(n => n + 1);
    };
</script>

<p>The count is: {$counter}</p>
<button on:click={increment}>Increment</button>

这里,$counter 表示订阅 counter 存储,当存储的值发生变化时,组件会自动重新渲染。counter.update 方法用于更新存储的值。

  1. 派生存储(Derived Stores) 有时候我们需要基于现有存储创建一个新的存储,这就用到了派生存储。例如,我们有一个 counter 存储,我们想创建一个新的存储 doubleCounter,它的值是 counter 的两倍。
// counter - store.js
import { writable, derived } from'svelte/store';

export const counter = writable(0);
export const doubleCounter = derived(counter, $counter => $counter * 2);

在组件中使用:

<!-- Counter.svelte -->
<script>
    import { counter, doubleCounter } from './counter - store.js';
    const increment = () => {
        counter.update(n => n + 1);
    };
</script>

<p>The count is: {$counter}</p>
<p>Double the count is: {$doubleCounter}</p>
<button on:click={increment}>Increment</button>

derived 函数的第一个参数是源存储,第二个参数是一个回调函数,当源存储的值变化时,回调函数会被调用并返回新的值给派生存储。

  1. 共享状态管理 对于多个组件需要共享的状态,可以将存储放在一个独立的文件中,然后在各个组件中导入使用。例如,在一个多页面应用中,用户登录状态可能需要在多个页面的组件中共享。
// auth - store.js
import { writable } from'svelte/store';

export const isLoggedIn = writable(false);

在不同的组件中:

<!-- Login.svelte -->
<script>
    import { isLoggedIn } from './auth - store.js';
    const handleLogin = () => {
        isLoggedIn.set(true);
    };
</script>

<button on:click={handleLogin}>Login</button>
<!-- Navbar.svelte -->
<script>
    import { isLoggedIn } from './auth - store.js';
</script>

{#if $isLoggedIn}
    <p>Welcome, user!</p>
{:else}
    <p>Please login.</p>
{/if}

这样通过共享 isLoggedIn 存储,不同组件可以同步获取和更新用户登录状态。

样式处理

  1. 组件内样式 Svelte 允许在组件内定义样式,这些样式只作用于该组件。例如:
<!-- Button.svelte -->
<script>
    let isClicked = false;
    const handleClick = () => {
        isClicked = true;
        console.log('Button clicked!');
    };
</script>

<button on:click={handleClick}>
    {isClicked? 'Clicked' : 'Click me'}
</button>

<style>
    button {
        background - color: blue;
        color: white;
        padding: 10px 20px;
        border: none;
        border - radius: 5px;
    }
    button:hover {
        background - color: darkblue;
    }
</style>

这种方式使得组件的样式封装性很好,不会影响其他组件。

  1. 全局样式 如果有一些样式需要应用到整个项目,可以在 main.js 中导入一个全局样式文件。首先创建一个 global.css 文件:
body {
    font - family: Arial, sans - serif;
    margin: 0;
    padding: 0;
}

然后在 main.js 中导入:

import './global.css';
import App from './App.svelte';

const app = new App({
    target: document.body
});

export default app;

这样全局样式就会应用到整个项目。

  1. 使用预处理器 Svelte 支持多种 CSS 预处理器,如 Sass、Less 和 Stylus。以 Sass 为例,首先安装 node - sasssvelte - preprocess
npm install node - sass svelte - preprocess

然后在 rollup.config.js 中配置:

import svelte from 'rollup - plugin - svelte';
import sveltePreprocess from'svelte - preprocess';

export default {
    input:'src/main.js',
    output: {
        sourcemap: true,
        format: 'iife',
        name: 'app',
        file: 'public/build/bundle.js'
    },
    plugins: [
        svelte({
            preprocess: sveltePreprocess({
                scss: {
                    includePaths: ['src']
                }
            })
        }),
        // other plugins...
    ]
};

之后就可以在 Svelte 组件中使用 Sass 语法:

<!-- Button.svelte -->
<script>
    let isClicked = false;
    const handleClick = () => {
        isClicked = true;
        console.log('Button clicked!');
    };
</script>

<button on:click={handleClick}>
    {isClicked? 'Clicked' : 'Click me'}
</button>

<style lang="scss">
    button {
        background - color: blue;
        color: white;
        padding: 10px 20px;
        border: none;
        border - radius: 5px;

        &:hover {
            background - color: darkblue;
        }
    }
</style>

使用预处理器可以提高样式编写的效率和灵活性。

路由管理

  1. Svelte Router 基础 在 Svelte 项目中,常用的路由库是 svelte - router - dom。首先安装它:
npm install svelte - router - dom

然后在 main.js 中配置路由:

import { Router, Route, Link } from'svelte - router - dom';
import App from './App.svelte';
import Home from './components/Home.svelte';
import About from './components/About.svelte';

const app = new App({
    target: document.body,
    props: {
        Router,
        Route,
        Link
    }
});

export default app;

App.svelte 中使用路由:

<script>
    import { Router, Route, Link } from'svelte - router - dom';
</script>

<Router>
    <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
    </nav>
    <Route path="/" component={Home} />
    <Route path="/about" component={About} />
</Router>

这里 Router 是路由的容器,Link 用于创建导航链接,Route 用于定义路径和对应的组件。

  1. 动态路由 有时候我们需要处理动态路由,例如展示用户详情页面,每个用户有不同的 ID。在 svelte - router - dom 中可以这样实现:
<!-- App.svelte -->
<script>
    import { Router, Route, Link } from'svelte - router - dom';
    import Home from './components/Home.svelte';
    import User from './components/User.svelte';
</script>

<Router>
    <nav>
        <Link to="/">Home</Link>
        <Link to="/user/1">User 1</Link>
    </nav>
    <Route path="/" component={Home} />
    <Route path="/user/:id" component={User} />
</Router>

User.svelte 组件中获取动态参数:

<!-- User.svelte -->
<script>
    import { page } from'svelte - router - dom';
    const { id } = $page.params;
</script>

<p>User ID: {id}</p>

这样就可以根据不同的用户 ID 展示相应的用户详情。

  1. 嵌套路由 对于一些复杂的页面结构,可能需要嵌套路由。例如,一个博客应用可能有文章列表页面,点击文章进入文章详情页面,文章详情页面又有评论和相关文章等子页面。
<!-- App.svelte -->
<script>
    import { Router, Route, Link } from'svelte - router - dom';
    import Blog from './components/Blog.svelte';
    import Article from './components/Article.svelte';
</script>

<Router>
    <nav>
        <Link to="/blog">Blog</Link>
    </nav>
    <Route path="/blog" component={Blog}>
        <Route path="article/:id" component={Article} />
    </Route>
</Router>

Blog.svelte 中可以有导航链接到嵌套的文章页面:

<!-- Blog.svelte -->
<script>
    import { Link } from'svelte - router - dom';
</script>

<ul>
    <li><Link to="article/1">Article 1</Link></li>
    <li><Link to="article/2">Article 2</Link></li>
</ul>

{#if $page && $page.children}
    {#each $page.children as child}
        <Route {...child} />
    {/each}
{/if}

通过这种方式可以实现复杂的嵌套路由结构。

构建与部署

  1. 开发环境构建 在开发过程中,我们希望能够快速看到代码更改的效果。使用 rollup - plugin - livereloadrollup - plugin - serve 可以实现这一点。在 rollup.config.js 中配置:
import svelte from 'rollup - plugin - svelte';
import commonjs from 'rollup - plugin - commonjs';
import resolve from 'rollup - plugin - resolve';
import livereload from 'rollup - plugin - livereload';
import serve from 'rollup - plugin - serve';

export default {
    input:'src/main.js',
    output: {
        sourcemap: true,
        format: 'iife',
        name: 'app',
        file: 'public/build/bundle.js'
    },
    plugins: [
        svelte(),
        resolve(),
        commonjs(),
        livereload('public'),
        serve({
            open: true,
            openPage: '/',
            host: 'localhost',
            port: 5000,
            contentBase: 'public'
        })
    ]
};

然后在 package.json 中添加脚本:

{
    "scripts": {
        "dev": "rollup - c - w"
    }
}

运行 npm run dev 就可以启动开发服务器,当代码发生变化时,浏览器会自动刷新。

  1. 生产环境构建 在生产环境中,我们需要优化代码以提高性能。首先在 rollup.config.js 中添加一些优化插件,如 terser 用于压缩代码:
import svelte from 'rollup - plugin - svelte';
import commonjs from 'rollup - plugin - commonjs';
import resolve from 'rollup - plugin - resolve';
import { terser } from 'rollup - plugin - terser';

export default {
    input:'src/main.js',
    output: {
        sourcemap: false,
        format: 'iife',
        name: 'app',
        file: 'public/build/bundle.js'
    },
    plugins: [
        svelte(),
        resolve(),
        commonjs(),
        terser()
    ]
};

package.json 中添加生产构建脚本:

{
    "scripts": {
        "build": "rollup - c"
    }
}

运行 npm run build 会生成优化后的生产代码,通常会减小文件体积,提高加载速度。

  1. 部署到不同平台
  • 静态服务器:Svelte 项目构建后生成的是静态文件,可以部署到任何静态文件服务器上,如 Netlify、Vercel 或 GitHub Pages。以 GitHub Pages 为例,首先确保项目已经托管在 GitHub 上,然后在项目设置中选择 master branch /docs folder(如果构建输出目录是 docs)或 master branch(如果构建输出目录是根目录下的 public)作为 GitHub Pages 的源。
  • Node.js 服务器:如果项目需要与后端 API 进行交互,可以将 Svelte 项目作为前端部分部署在 Node.js 服务器上。例如,使用 Express 框架,首先安装 Express:
npm install express

然后创建一个 server.js 文件:

const express = require('express');
const app = express();
const path = require('path');

app.use(express.static(path.join(__dirname, 'public')));

const port = process.env.PORT || 3000;
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

运行 node server.js 就可以启动服务器并提供 Svelte 应用。

通过以上这些最佳实践,可以构建出高效、可维护且易于扩展的 Svelte 项目。从项目初始化到组件设计、状态管理、样式处理、路由管理以及最后的构建与部署,每个环节都紧密相连,共同打造出优秀的前端应用。