编译型JSS框架Linaria的原理

更新日期: 2020-10-22阅读: 1.4k标签: 框架

Linaria 是一个近似于 styled-components 和 emotion JSS 框架,不同点在于, styled-components 和 emotion 是一个 运行时 方案,而 Linaria 是一个 编译期 + 运行时 方案。

运行时的 JSS 方案必须内置一个 css 处理器,并且在运行时去解析,分别增加了体积和性能上的成本。 Linaria 创造性的在编译期将相应的 JSS 解析出来,抽出解压到一个 CSS 文件中,并将相应的 JSS 代码替换成一个指向某个 css 类名的字符串,避免了运行时方案的问题。


总览

对于以下代码

import { css } from '@linaria/core'

let size = 5;
size = (function() { return 3; }());

const headerClassName = css`
  text-align: center;
  color: #fff;
`;

const headerTitleClassName = css`
  .${headerClassName} {
    font-size: ${size}px;
  }
`;

console.log(headerClassName, headerTitleClassName);

Linaria 会将其编译为

// index.bundle.js
var size = 5;

size = function () {
  return 3;
}();

var headerClassName = "hf71da1";
var headerTitleClassName = "hi1y09m";
console.log(headerClassName, headerTitleClassName);
/* index.css */
.hf71da1{text-align:center;color:#fff;}
.hi1y09m .hf71da1{font-size:3px;}

通过编译后的代码我们可以看出:

  • 相关 JSS 代码在编译后被抽离解压到一个 .css 文件中了
  • 对应 JSS 代码被替换成了一个哈希字符串,这个字符串代表某个样式表的类名
  • 编译时解压并非是静态解析,而是动态执行,可以看到样式表 font-size 的值为 3 而非 size 变量的初始值

原理

Linaria 的实现依赖于 wbepack(rollup) 和 babel 是必然的,使用 Linaria 必须在 webpack 和 babel 分别设置 @linaria/webpack4-loader 和 @linaria/babel 才可以。


流程

通过 webpack 使用 Linaria 需要 .rules 上进行如下配置

{
  test: /.(js|ts)$/,
  use: [
    'babel-loader',
    '@linaria/webpack4-loader''
  ],
},

所有的 JS 代码都会经过 @linaria/webpack4-loader ,其核心代码如下

export default function index(
  this: LoaderContext,
  sourceCodes: string,
  inputSourceMap: RawSourceMap | null
) {

  result = transform(sourceCodes, {
    filename: path.relative(process.cwd(), this.resourcePath),
    inputSourceMap: inputSourceMap ?? undefined,
    outputFilename,
    pluginOptions: rest,
    preprocessor,
  });

  if (result.cssText) {
    let { cssText } = result;

    if (sourceMap) {
      cssText += `/*# sourceMappingURL=data:application/json;base64,${Buffer.from(
        result.cssSourceMapText || ''
      ).toString('base64')}*/`;
    }

    if (result.dependencies?.length) {
      result.dependencies.forEach((dep) => {
        try {
          const f = resolveSync(path.dirname(this.resourcePath), dep);

          this.addDependency(f);
        } catch (e) {
          // eslint-disable-next-line no-console
          console.warn(`[linaria] failed to add dependency for: ${dep}`, e);
        }
      });
    }

    this.callback(
      null,
      `${result.code}\n\nrequire(${loaderUtils.stringifyRequest(
        this,
        outputFilename
      )});`,
      result.sourceMap ?? undefined
    );
    return;
  }

是一个标准的 webpack 异步 loader ,@linaria/webpack4-loader 做的事情就是把传入的源代码当做参数传入、调用 transform 函数,这个函数来自 @linaria/babel ,@linaria/babel 是 Linaria 维护的解析 JSS 代码的 babel-preset 。

export default function transform(code: string, options: Options): Result {
  // Check if the file contains `css` or `styled` words first
  // Otherwise we should skip transforming
  if (!/\b(styled|css)/.test(code)) {
    return {
      code,
      sourceMap: options.inputSourceMap,
    };
  }
  // ...  
  const ast = parseSync(code, {
    ...babelOptions,
    filename: options.filename,
    caller: { name: 'linaria' },
  });

  const { metadata, code: transformedCode, map } = transformFromAstSync(
    ast!,
    code,
    //.....
  )!;

  const {
    rules,
    replacements,
    dependencies,
  } = (metadata as babel.BabelFileMetadata & {
    linaria: LinariaMetadata;
  }).linaria;
  const mappings: Mapping[] = [];

  let cssText = '';

  Object.keys(rules).forEach((selector, index) => {
    mappings.push({
      generated: {
        line: index + 1,
        column: 0,
      },
      original: rules[selector].start!,
      name: selector,
      source: '',
    });

    // Run each rule through stylis to support nesting
    cssText += `${preprocessor(selector, rules[selector].cssText)}\n`;
  });

  return {
    code: transformedCode || '',
    cssText,
    rules,
    replacements,
    dependencies,
    sourceMap: map
  };
}

transform 内部首先会判断代码是否使用 Linaria 的 api ,如果使用了则解析相应的 JSS 代码,**并执行相关的 JS 代码,**这也是为什么本文开头例子处编译后样式表的 font-size 的值为 3 而非 5 的原因。解析的时候,@linaria/babel 会将 JSS 中的 css 代码写到 metadata 里,而非转化后的源代码处,随后交付给 @linaria/webpack4-loader 处理。

metadata 是 babel 解析、转译代码时用于储存一些辅助信息的地方,其内容适用于辅助转译流程的,转译完成后就不存在了

也就是说,@linaria/babel 只负责将 JSS 代码根据文件路径计算出一个哈希名,作为类名,替换相应的JSS 代码。 css 解压相关的操作由 @linaria/webpack4-loader 负责。

@linaria/webpack4-loader 会将 metadata 里 css 解压到 .linaria-cache/index.linaria.css 里,然后在转化后的源代码末尾处加上 require("./.linaria-cache/index.linaria.css") ,随后 webpack 解析时就会将解析 .css 文件相关操作委托到处理 .css 文件的 loader ,此时我们可以自由选择使用 mini-css-extract-plugin 还是 style-loader ,或者其他的 .css loader ,重点在于我们拥有了 webpack 和 .css 文件相关的生态,而其他的 JSS 方案就没法做到这点。


总结

  1. JS 代码经由 @linaria/webpack4-loader
  2. @linaria/webpack4-loader 内部使用 @linaria/babel 解析、执行 JS 代码
  3. @linaria/babel 将 JSS 编译成根据文件路径生成的类名,将 css 代码写入到生成的 metadata ,将其交付到 @linaria/webpack4-loader
  4. @linaria/webpack4-loader 将 metadata 的 css 代码解压到 /.linaria-cache 文件中
  5. @linaria/webpack4-loader 在解析后代码末尾处加上 require("./.linaria-cache/index.linaria.css") ,交由 webpack 进行后续的处理

作者:塔希
链接:https://juejin.cn/post/6897763694491631624
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


链接: https://www.fly63.com/article/detial/9864

Angular、Vue、React 和前端的未来

越来越多的人开始站队 Angular、Vue、React,仅仅围绕这些库或者框架进行前端技术讨论,这实在不是什么好的现象。其实我想基于我个人的经验聊下前端的演进和未来,希望可以贡献微薄的力量,消除一些我个人认为的前端社区不太好的风气。

JavaScript 框架的探索与变迁

近几年可谓是 JavaScript 的大爆炸纪元,各种框架类库层出不穷,它们给前端带来一个又一个的新思想。从以前我们用的 jQuery 直接操作 DOM,到 BackboneJS、Dojo 提供监听器的形式,在到 Ember.js、AngularJS 数据绑定的理念,再到现在的 React、Vue 虚拟 DOM 的思想。

新框架(新工具,语言)从入门到精通的正确姿势

新框架(新工具,语言),一、了解概念,把握思路,二、迅速实战,见招拆招,三、深入文档,求人不如求己,四、掌握原理,有恃无恐,五、源码分析,自立门户。如果你已经熟悉一门计算机语言,当再学习其他语言的时候,会发现他们几乎是大同小异,对比着学习,会令你事半功倍。

现代 js 框架存在的根本原因

我曾见过很多很多人盲目地使用(前端)框架,如 React,Angular 或 Vue等等。这些框架提供了许多有意思的东西:它们支持组件化;它们有强大的社区支持;它们有很多(基于框架的)第三方库来解决问题;它们有很多(很好的)第三方组件;它们有浏览器扩展工具来帮助调试;它们适合做单页应用。

Uber开源Fusion.js:一个基于插件架构的通用Web框架

Web 技术变化得很快,而最佳技术实践也在不断发展。Uber 的 Web 平台团队开发了 Fusion.js,一个开源的 Web 框架,用于简化 Web 开发,并构建出高性能的轻量级 Web 应用程序。

web前端框架选择_前端框架是解药还是毒药?

要使用现代的前端框架,你需要下载开发环境和依赖,编译代码,然后在浏览器上运行。这个是好是坏?究竟是什么导致了这种不必要的复杂性?是因为我们构建的网站太复杂,还是因为框架本身就很复杂?

前端框架选型

有一个流传较广的笑话,一个人在stackoverflow中提了一个问题,如何使用javascript实现一个数字与另外一个数字相加。最高票回答是你应该使用jQuery插件,jQuery插件可以做任何事情。 历史总是在重演,以前是jQuery,现在可能是react或vue。不同的框架有不同的应用场景,杀鸡不要用牛刀

基于wsgiref模块DIY一个web框架

Web框架(Web framework)是一种开发框架,用来支持动态网站、网络应用和网络服务的开发。这大多数的web框架提供了一套开发和部署网站的方式,也为web行为提供了一套通用的方法

Ionic 框架宣布 2019 年将正式支持 Vue 和 React

Ionic 是一个高级的 HTML5 移动端应用框架,也是一个开发混合移动应用的前端框架,旨在让 Web 开发者更轻松地构建、测试、部署和监控跨平台应用。Ionic 基于 Angular 语法,之前一直不支持 Vue 和 React 。

Nancy_轻量级的Web框架

最近想找一个简单的.Net下的轻量级Web框架,作为用户的本地的一个WebServer,实现同浏览器程序的一些简单交互,并调用本地服务,实现类似浏览器插件的功能。它有如下几点要求:

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!