Parcel:Webpack之外的选择

1,239 阅读5分钟

Parcel是什么

Parcel 是一个可用于构建网站前端应用(如React, Vue)和npm包(如React组件库)构建工具

比起大名鼎鼎的Webpack,它所带来的便是开箱即用

许多时候,我们并不需要对项目的构建过程进行深度定制。此时使用Parcel的优势就体现出来了。我们不需要写一堆Webpack的配置,即使用脚手架生成,它也会在我们的源代码仓库中留下一堆配置文件。

此时便是Parcel大显身手的时刻,干净利落地打包。

Parce快速入门

mkdir parcel-start && cd parcel start
yarn init -y

接着便是安装Parcel依赖

yarn add -D parcel

接下来,我们需要一个入口文件和一个脚本文件,除此之外,如果有一个全局的样式文件,那就更美妙了

src/index.html

<!doctype html>
    <html lang="en">
    <head>
        <meta charset="utf-8"/>
        <title>A simple Parcel App</title>
        <link rel="stylesheet" href="global.less" />
    </head>
    <body>
        <script type="module" src="index.ts"></script>
    </body>
</html>

src/index.ts

const appRootEl: HTMLDivElement = document.createElement("div");
appRootEl.id = "app-root";
appRootEl.innerHTML = "Hello, world";
document.body.appendChild(appRootEl);

global.less

#app-root {
  background-color: fade(pink, 20%);
}

万事俱备,只欠东风,现在就让它跑起来。这极其容易,只要给Parcel一个打包的入口,那么它能替你办好剩下的一切

yarn parcel src/index.html

image.png 访问http://localhost:1234 按照预期,我们看到的页面应该是这样的

image.png

作为入口的 index.html引入了index.ts,创建了一个内容为Hello, world的div元素。同时入口也引入了global.less,在Less中,我们使用了fade 函数来改变了粉色的透明度。

显然,我们并没有做任何关于Typescript和Less的配置,仅仅只是创建了这两个语言相关的文件。Parcel就非常智能地做到了.ts -> .js.less -> .css的转换,并将他们打包。

React和Vue

作为目前前端主流框架的React和Vue,他们都对Javascript语法进行了扩展,因此,如果想要完整体验他们带来的便利,离不开构建工具的帮助。 因此,Parcel也将他们囊括其中了。

React

在这之前我们需要安装reactreact-dom

yarn add react react-dom

在React中,除了React组件,我们还需要一些方案支持样式,最经典的就是CSS modules。

index.html

<!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="utf-8" />
    <title>Parcel React App</title>
    </head>
    <body>
        <div id="app"></div>
        <script type="module" src="index.jsx"></script>
    </body>
</html>

src/index.jsx

import ReactDOM from "react-dom";
import { App } from "./App";

const app = document.getElementById("app");
ReactDOM.render(<App />, app)

App.jsx

import * as classes from './App.module.less';
export function App() {
  return <h1 className={classes.app}>Hello React</h1>;
}

global.less

#app-root {
        width: 100%;
        height: 100%;
}

App.module.less,Parcel约定,*.module.结尾的文件,为 CSS module

.app {
    font-size: 23px;
    color: fade(pink, 60%);
}

最后就是让它们组合到一起跑起来

yarn parcel src/index.html

image.png

Vue

同样地,我们安装依赖

yarn add vue
``` html
`index.html`
<!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="utf-8" />
    <title>Parcel Vue App</title>
    </head>
    <body>
        <div id="app"></div>
        <script type="module" src="index.js"></script>
    </body>
</html>

index.js

import { createApp } from "vue";
import App from "./App.vue";

const app = createApp(App);
app.mount("#app");

App.vue

<template>
  <button @click="increment" class="app">
    Count is: {{ state.count }} Double is: {{
    state.double }}
  </button>
</template>

<script>
  import { reactive, computed } from "vue";

  export default {
    setup() {
      const state = reactive({
        count: 0,
        double: computed(() => state.count * 2),
      });

      function increment() {
        state.count++;
      }

      return {
        state,
        increment,
      };
    },
  };
</script>

<style lang="less" scoped>
    .app {
        background: fade(pink, 80%);
        padding: 10px;
        border-radius: 5px;
        margin: 10px;
    }
</style>

同样的命令我们让它运行起来

yarn parcel src/index.html

image.png

对于Vue和React项目,他们的webpack配置会有很多不同,但在上面这两个例子,显然我们能深刻体会到“开箱即用”带来的收益。

PS: 如果想要同时在一个项目中跑Vue和React web app,需要使用正规的微前端框架并将Vue和React的依赖安装在两个sub package中,否则Parcel会不知道jsx是要转化成Vue还是React的jsx

图片资源

与Webpack不同,对于image,Parcel提供了更多的功能,因此写法上也有差异。 在Javascript中,需要这样引入一张图片

const imageUrl = new URL(
  'image.jpeg?as=webp&width=250',
  import.meta.url
);

HTML和css中则简单很多

<html>
  <head>
    <meta charset="utf-8" />
    <title>HTML Example</title>
  </head>
  <body>
    <picture>
      <source srcset="image.jpeg?as=avif&width=800" type="image/avif" />
      <source srcset="image.jpeg?as=webp&width=800" type="image/webp" />
      <source srcset="image.jpeg?width=800" type="image/jpeg" />
      <img src="image.jpeg?width=200" alt="test image" />
    </picture>
  </body>
</html>
body {
  background: url('images/background.png');
}

比起url-loader带来的简单的import imageURL from 'someImage.png'new URL更加复杂,但它也带来了下列能力

  • 更改图片尺寸,通过width, height参数
  • 更改图片质量,通过quality参数,0到100之间的百分比,数值越大图片越清晰,数值越小图片体积越小
  • 转换图片类型,通过as参数,可以将图片转化为webp, jpeg/jpg, png, avif

React SVGR

对于SVG图片它的引入方式与一般图片相同。但如果你是React框架的使用者,那么SVGR也许更适合。它能将引入的svg资源自动转化成React组件。 此处,我们需要进行一些微量的配置

yarn add -D @parcel/transformer-svg-react

在项目根目录下创建一个.parcelrc文件

{
  "extends": "@parcel/config-default",
  "transformers": {
    "*.svg": ["...", "@parcel/transformer-svg-react"]
  }
}

然后就可以将svg作为React组件引入

import AddIcon from "./AddIcon.svg";

export function AddButton() {
  return (
    <button aria-label="Add">
      <AddIcon />
    </button>
  );
}

node环境变量

对于web app来说,它的执行环境中是不存在process.env的。我们能在我们的代码中使用process.env.NODE_ENV等环境变量,是在编译时被替换成编译环境下的环境变量。Webpack使用EnvironmentPlugin来进行环境变量的替换。

但对于Parcel来说,我们只要直接读取process.env中的字段,Parcel检测到相关代码后便会自动从当前编译时环境变量中取值并替换。下列两种方法都是可以的。

console.log(process.env.API_KEY);
const { API_KEY } = process.env;

除此之外,当我们需要批量设置环境变量时,Parcel支持.env.env.production.env.development 等环境变量配置文件,当Parcel进入构建时,会自动将.env*中的环境变量配置加载到node执行上下文中。 .env文件中支持NAME=value这样的键值对。下面便是一个例子

APP_NAME=test
API_KEY=12345

如果NODE_ENV="production",那么.env.development.nev.development.local将不会被用到,在NODE_ENV="development"中同理

他们的优先级为.env.local > .env.production.local > .env.production > .env

构建

同样构建命令与watch命令大同小异

yarn parcel build src/index.html

最终的构建产物便会在dist目录下。如果需要输出到其它地方,请使用--dist-dir 参数

另外,在生产环境中,我们构建出的图片,js脚本等静态资源并不会和HTML文件存放于同一处而是在CDN上。因此我们需要将资源的路径也指向CDN上。

通过传递--public-url参数,就能指定资源在CDN上的位置

yarn parcel build src/index.html --public-url="https://cdn.ringcentral.com/glip"

总结

这些场景下,比起Webpack用Parcel更好用

  • 假如项目使用Webpack,用脚手架生成Webpack的配置之后便不再更改Webpack配置,并且你不需要对 webpack dev server进行一些配置
  • Demo或者快速写一个原型时
  • 发布到npm上的包
  • Chrome扩展插件 但目前来说,相对于Webpack,Parcel被用于构建大型项目的经验还比较少,也许在未来,当它的插件生态更加丰富的时候,便是从Webpack口中抢下更多市场的时候

参考

Parcel (parceljs.org)