创造你自己的插件

一个Snowpack 插件可以让你用新的行为扩展 Snowpack。插件可以与 Snowpack 构建管道的不同阶段挂钩,以增加对新文件类型和你最喜欢的开发工具的支持。添加插件以支持 Svelte,将 Sass 编译为 CSS,将 SVG 转换为 React 组件,打包你的最终构建,在开发过程中进行类型检查,以及更多。

本指南将带领你创建和发布你的第一个插件。

  • Snowpack 插件的基本结构
  • 如何从 Snowpack Plugin API 中选择正确的钩子
  • 如何发布你的插件并将其添加到我们的插件目录中

先决条件:Snowpack 插件是用 JavaScript 编写的,并通过 Node.js 运行,因此需要具备这两方面的基本知识。

创建和测试你的第一个插件

在这一步中,你将创建一个简单的插件支架,你可以根据指南中的例子把它变成一个有效的插件。

为你的插件创建一个名为my-snowpack-plugin的目录,并在其中创建一个my-snowpack-plugin.js文件。

// my-snowpack-plugin.js
// Example: a basic Snowpack plugin file, customize the name of the file and the value of the name in the object
// snowpackConfig = The Snowpack configuration object
// pluginOptions = user-provided configuration options
module.exports = function (snowpackConfig, pluginOptions) {
  return {
    name: "my-snowpack-plugin",
  };
};

为了测试你的新插件,运行npm init来创建一个基本的package.json,然后在你的插件目录中运行npm link,在全局范围内(在你的开发机器上)公开该插件。

为了测试,在不同的目录下创建一个新的 Snowpack 示例项目。在你的示例 Snowpack 项目中,运行npm install && npm link my-snowpack-plugin(使用你的插件的package.json中的名称)。

另一种方法是使用npm install --save-dev path_to_your_plugin,这将在你的 Snowpack 项目的package.json 中创建 “类似 symlink “的条目。

在你的示例 Snowpack 项目中,将你的插件和任何你想测试的插件选项一起添加到snowpack.config.mjs中。

// snowpack.config.mjs
// Example: enabling a Snowpack plugin called "my-snowpack-plugin"
export default {
  plugins: ["my-snowpack-plugin"],
};

测试和故障排除

  • TODO: 创建一个完整的测试程序
  • 提示:在命令中添加--verbose以查看步骤,例如,snowpack dev --verbosesnowpack build --verbose

为你的插件添加用户可配置的选项

TODO 让这成为一个真实的例子 在这一步,你将学习如何为你的插件添加用户可配置的选项,并在你的插件代码中使用它们。

在你的例子 Snowpack 项目中,不要把插件启用为一个包含插件名称的字符串,而是使用一个数组。第一项是你的插件的名称,第二项是包含插件选项的新对象。

  // snowpack.config.mjs
  export default {
    plugins: [
-     'my-snowpack-plugin'
+     ['my-snowpack-plugin', { optionA: 'foo', optionB: true }]
    ]
  };

你可以通过pluginOptions来访问这些

  // my-snowpack-plugin.js
  module.exports = function (snowpackConfig, pluginOptions) {
+   let optionA = pluginOptions.optionA;
+   let optionB = pluginOptions.optionB;
    return {
      name: 'my-snowpack-plugin',
    };
  };

插件使用案例

Snowpack 使用一个内部的构建管道来构建你的应用程序中的文件,用于开发和生产。每个源文件都会经过构建管道,这意味着 Snowpack 可以构建的不仅仅是 JavaScript。图片、CSS、SVG 等都可以由 Snowpack 构建。

构建插件

Snowpack 找到第一个声称可以解决给定文件的插件。然后它调用该插件的load()方法,将文件加载到你的应用程序中。这就是编译语言(TypeScript、Sass、JJSX 等)被加载并编译成可以在网络上运行的东西(JS、CSS 等)。

转换插件

一旦加载,每个文件都会再次通过构建管道,通过所有提供该方法的插件的匹配转化()方法运行。插件可以在完成文件构建之前对文件进行转换以修改其内容。

开发工具插件

Snowpack 插件支持一个run()方法,它可以让你运行任何 CLI 工具并将其输出连接到 Snowpack 中。你可以用它来运行你最喜欢的开发工具(linters,TypeScript 等)与 Snowpack,并通过 Snowpack 开发者控制台自动报告其输出。如果命令失败,你可以选择让你的生产构建失败。

打包工具插件

Snowpack 默认为你构建一个可运行的、未打包的网站,但你可以通过插件optimize()方法用你喜欢的打包工具(webpack、Rollup、Parcel 等)来优化这个最终构建。当使用打包插件时,Snowpack 会在你的构建中自动运行打包工具来优化它。

请参阅我们的官方@snowpack/plugin-webpackbundler 插件,了解使用当前接口的例子。

例子:入门

要创建一个 Snowpack 插件,你可以从以下文件模板开始。

// my-snowpack-plugin.js
module.exports = function (snowpackConfig, pluginOptions) {
  return {
    name: "my-snowpack-plugin",
    // ...
  };
};
// snowpack.config.mjs
export default {
  plugins: [
    [
      "./my-snowpack-plugin.js",
      {
        optionA: "foo",
        optionB: "bar",
      },
    ],
  ],
};

一个 Snowpack 插件应该以一个函数的形式发布,这个函数可以通过调用插件特定的选项来返回一个插件对象。 Snowpack 会自动调用这个函数来加载你的插件。该函数接受 2 个参数,按照这个顺序。

  1. Snowpack 配置对象(snowpackConfig)
  2. (可选)用户提供的配置选项(pluginOptions)。

例子:转换一个文件

对于我们的第一个例子,我们将看看如何转换一个文件。

module.exports = function (snowpackConfig, pluginOptions) {
  return {
    name: "my-commenter-plugin",
    async transform({ id, contents, isDev, fileExt }) {
      if (fileExt === ".js") {
        return `/* I’m a comment! */ ${contents}`;
      }
    },
  };
};

这个函数返回的对象是一个Snowpack Plugin。一个插件由一个名称属性和一些进入 Snowpack 生命周期的钩子组成,以定制你的构建管道或开发环境。在上面的例子中,我们有。

  • 名称属性。你的插件的名称。如果发布到 npm,这通常与你的软件包名称相同。
  • 转换方法。一个允许你转换和修改已建文件的函数。在这种情况下,我们在你构建的每个 JS 文件的开头添加一个简单的注释(/*我是一个注释*/)。

这涵盖了单文件转换的基础知识。在下一个例子中,我们将展示如何编译一个源文件并在此过程中改变文件扩展名。

例子:从源码构建

当你从源代码构建文件时,你也有能力将文件类型从源代码转换为网络代码。在这个例子中,我们将使用 Babel 加载几种类型的文件作为输入,并在最终构建时输出 JavaScript。

const babel = require("@babel/core");

module.exports = function (snowpackConfig, pluginOptions) {
  return {
    name: "my-babel-plugin",
    resolve: {
      input: [".js", ".jsx", ".ts", ".tsx", ".mjs"],
      output: [".js"],
    },
    async load({ filePath }) {
      const result = await babel.transformFileAsync(filePath);
      return result.code;
    },
  };
};

这是官方 Snowpack Babel 插件的简化版,它用load()方法构建应用程序中的所有 JavaScript、TypeScript 和 JSX 文件。

load()方法负责从磁盘加载和构建文件,而resolve属性则告诉 Snowpack 插件可以加载哪些文件,以及期望输出什么。在这种情况下,该插件要求负责匹配在resolve.input 中发现的任何文件扩展名的文件,并输出.jsJavaScript(通过resolve.output 声明)。resolve.output也可以使用多部分扩展名,如.module.css.hbs.js-文件将从最具体的扩展名到最小的扩展名进行匹配。

请看它的操作。假设我们有一个源文件在src/components/App.jsx。因为.jsx文件的扩展名与我们的插件的resolve.input数组中的扩展名相匹配,所以 Snowpack 让这个插件负责加载这个文件。load()执行,Babel 从磁盘中构建 JJSX 输入文件,JavaScript 被返回到最终构建。

例子:多文件构建

对于一个更复杂的例子,我们将采取一个输入文件(.svelte),并使用它来生成两个输出文件(.js.css)。

const fs = require("fs").promises;
const svelte = require("svelte/compiler");

module.exports = function (snowpackConfig, pluginOptions) {
  return {
    name: "my-svelte-plugin",
    resolve: {
      input: [".svelte"],
      output: [".js", ".css"],
    },
    async load({ filePath }) {
      const fileContents = await fs.readFile(filePath, "utf-8");
      const { js, css } = svelte.compile(fileContents, { filename: filePath });
      return {
        ".js": js && js.code,
        ".css": css && css.code,
      };
    },
  };
};

这是官方 Snowpack Svelte 插件的一个简化版本。如果你不熟悉 Svelte,不要担心,只要知道构建一个 Svelte 文件(.svelte)会生成 JS 和 CSS,供我们最终构建。

在这种情况下,resolve属性只接受一个输入文件类型(['.svelte']),但有两个输出文件类型(['.js', '.css'])。这与 Svelte 的构建过程和我们的load()方法的返回项相匹配。

请看它的操作。假设我们有一个源文件在src/components/App.svelte。因为.svelte文件的扩展名与我们的插件的resolve.input数组中的扩展名相匹配,所以 Snowpack 让这个插件负责加载这个文件。load()执行后,Svelte 从磁盘中构建文件,JavaScript 和 CSS 都被返回到最终构建中。

请注意,.svelteresolve.output中没有出现,也没有被load()返回。只有load()方法返回的文件才包括在最终构建中。如果你想让你的插件在最终构建中保留原始源文件,你可以在返回对象中添加{ '.svelte': contents }。到返回对象中。

例子:服务器端渲染(SSR)

插件可以通过load()插件钩子为 SSR 产生服务器优化的代码。isSSR标志告诉插件,Snowpack 正在为服务器请求你的文件,并且它将期待一个在服务器上运行的响应。

一些框架/语言(如 React)在浏览器和服务器上都运行相同的代码。其他的(如 Svelte)将为服务器创建不同于浏览器的输出。在下面的例子中,我们使用isSSR标志来告诉 Svelte 编译器在 Snowpack 的要求下生成服务器优化的代码。

const svelte = require("svelte/compiler");
const fs = require("fs");

module.exports = function (snowpackConfig, pluginOptions) {
  return {
    name: "basic-svelte-plugin",
    resolve: {
      input: [".svelte"],
      output: [".js", ".css"],
    },
    async load({ filePath, isSSR }) {
      const svelteOptions = {
        /* ... */
      };
      const codeToCompile = fs.readFileSync(filePath, "utf-8");
      const result = svelte.compile(codeToCompile, {
        ...svelteOptions,
        ssr: isSSR,
      });
      // ...
    },
  };
};

如果你不确定你的插件是否需要特殊的 SSR 支持,你可能可以跳过这一点,忽略插件中的isSSR标志。许多语言不需要这个,而且 SSR 总是由用户有意选择的。

例子:优化和打包

Snowpack 通过optimize()钩子支持可插拔的打包插件和其他构建优化。该方法在构建后运行,给插件一个优化最终构建目录的机会。Webpack、Rollup 和其他只针对构建的优化应该使用这个钩子。

module.exports = function(snowpackConfig, pluginOptions) {
  return {
    name: 'my-custom-webpack-plugin',
    async optimize({ buildDirectory }) {
      await webpack.run({...});
    }
  };
};

这是@snowpack/plugin-webpack插件的一个(明显)简化版本。当构建命令完成构建你的应用程序时,这个插件钩子被调用,并以buildDirectory路径作为参数。由该插件从该目录读取构建文件,并将任何更改写回该目录。更改应该在原地进行,所以只在最后写入文件,并确保自己清理干净(如果一个文件在优化/打包后不再需要,就可以安全地删除)。

测试

要开发和测试一个 Snowpack 插件,其策略与其他 npm 包相同。

  • 创建你的新插件项目(可以用npm inityarn init),例如 npm name:my-snowpack-plugin,并在其中粘贴上述的代码片段
  • 在你的插件的项目文件夹中运行npm link,在全局范围内公开该插件(就你的开发机器而言)。
  • 在不同的地方创建一个新的 Snowpack 示例项目进行测试
  • 在你的示例 Snowpack 项目中,运行npm install && npm link my-snowpack-plugin(使用你的插件的package.json中的名称)。
    • 请注意,npm install会删除你的链接插件,所以在任何安装中,你都需要重新做npm link my-snowpack-plugin
    • (另一种方法是使用npm install --save-dev ,这将在你的 Snowpack 项目的package.json 中创建 “symlink-like “条目)

在你的示例 Snowpack 项目中,将你的插件和任何你想测试的插件选项一起添加到snowpack.config.mjs中。

export default {
  plugins: [
    [
      "my-snowpack-plugin",
      {
        "option-1": "testing",
        "another-option": false,
      },
    ],
  ],
};

发布插件

要与世界分享一个插件,你可以把它发布到 npm。例如,看看snowpack-plugin-starter-template,它可以让你快速启动和运行。你可以直接复制它,也可以简单地拿走你需要的东西。

一般来说,请确保注意以下的检查清单。

  • ✔️ 你的package.json文件有一个指向最终构建的条目
  • ✔️ 你的代码被编译成可以在 Node >= 10 上运行。
  • ✔️ 你的软件包 README 包含一个自定义选项列表,如果你的插件是可配置的。

提示/问题

  • 请记住。一个源文件将总是被第一个load()插件加载,以索取它,但构建结果将通过每个转换函数运行。
  • Snowpack 将始终保持原始文件名(App),只在构建过程中改变扩展名。
  • Snowpack 中的扩展名总是有一个领先的.字符(例如:.js.ts)。这是为了匹配 Node 的path.extname()行为,同时确保我们不匹配扩展名子串(例如,如果我们在文件末尾匹配js,我们也不希望意外地匹配.mjs文件;我们希望在这里明确一点)。
  • resolve.inputresolve.output文件扩展数组对于 Snowpack 如何理解你的构建管道至关重要,并且总是需要load()正确运行。
  • 如果load()没有返回任何东西,该文件就不会被加载,而会调用下一个合适的插件的load()
  • 如果transform()没有返回任何东西,文件就不会被转化。
  • 如果你想建立一个只在初始化时运行一些代码的插件(比如@snowpack/plugin-dotenv),把你的副作用代码放在返回插件的函数里面。但一定要确保仍然返回一个插件对象。一个简单的{ name }对象就可以了。