# SASS

提示

本节为可选内容,Less (opens new window)Stylus (opens new window) 的使用与本节 Sass (opens new window) 类似。

由于 CSS 缺少可编程性,写出来的代码会有大量重复。一般项目中会使用 CSS 预处理工具,SassLessStylus。这里将使用 Sass 作为参考,其他工具类似。

# 改造

为了后续内容的展开,我们需要创建 src/style/element.scss 文件对 src/style/element.css 内容进行改造迁移。并且将 src/element.ts.css 的引用改为该文件。


 
 



 



 





// src/style/element.scss
.ui {
  &-random-text {
    display: inline-block;
    font-size: 16px;
    font-weight: bold;
    color: coral; // 顺便改个颜色
    writing-mode: vertical-lr;
  }

  &-backgroud-body {
    border-radius: 50%;
    box-shadow: grey 10px 10px 10px 0;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

 













// src/element.ts
import "./style/element.scss";
// import "./style/element.css";
import * as util from "./util";

// 使用 background 显示图片
export async function createBackgroudImg(src: string) {
  // ...
}

// 创建一个有随机数的节点
export function createRandomTextElement() {
  // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

运行 node build.js,结果就是编译失败。原因和之前的章节类似,目前无法为识别 .scss 文件,需要做一些处理。

# 整体编译

处理 .scss 文件就需要用到 sass (opens new window) 包。如果是要想 Webpack 能处理 scss 文件,那么还需要给 Webpack 加一个 loader。将 .scss 文件编译成 .css 再交给 css-loader 做处理,这个 loader 就是 sass-loader (opens new window)

# 安装
npm install sass --save-dev
# 如果是在 Webpack 使用还需要安装以下
npm install sass-loader --save-dev
1
2
3
4







 















 










// ...
webpack(
  {
    // ...
    module: {
      rules: [
        {
          test: /\.(css|scss)$/,
          use: [
            MiniCssExtractPlugin.loader,
            "css-loader",
            {
              loader: "postcss-loader",
              options: {
                postcssOptions: {
                  plugins: [
                    require("autoprefixer")({
                      overrideBrowserslist: ["ie >= 11"],
                    }),
                  ],
                },
              },
            },
            "sass-loader",
          ],
        },
        // ...
      ],
    },
    plugins: [new MiniCssExtractPlugin()],
  }
  // ...
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

运行 node build.js,重新在浏览器打开 index.html,均正常运行,并且颜色也调整了。

Chrome 运行效果图

# 单独编译

和前面几章一样。还要对按需加载方式的文件进行处理,根据 Nodejs API 官方文档 (opens new window)compile.js 进行调整:







 










 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

































// compile.js
// 相当于 run-babel.js 的升级版
var stream = require("stream");
var { src, dest } = require("gulp");
var babel = require("@babel/core");
var postcss = require("postcss");
var sass = require("sass");

function compileTS(modules) {
  // ...
}

function compileCSS(modules) {
  // Commonjs 输出至 ./lib
  // ES6 Module 输出至 ./es
  var path = modules === false ? "./es" : "./lib";

  // 读取 scss 文件,而不是 css 了
  src("./src/**/*.scss")
    .pipe(
      // 将 scss 转化成 css
      new stream.Transform({
        objectMode: true,
        transform: function(chunk, encoding, next) {
          var result = sass.renderSync({ file: chunk.path });
          chunk.contents = Buffer.from(result.css);
          // 后缀名文件 .scss -> .css
          chunk.extname = ".css";
          next(null, chunk);
        },
      })
    )
    .pipe(
      // 上面处理好的 css 再进行兼容性处理
      new stream.Transform({
        objectMode: true,
        transform: function(chunk, encoding, next) {
          // 传入配置
          postcss([
            require("autoprefixer")({
              // 可以对比添加前后的产物有什么区别
              // 注意这里是不兼容 IE <= 11,为了查看添加前后的区别
              // overrideBrowserslist: ["> 1%", "not ie <= 11"],
              overrideBrowserslist: ["ie >= 11"],
            }),
          ])
            .process(chunk.contents.toString(encoding), { from: chunk.path })
            .then((result) => {
              chunk.contents = Buffer.from(result.css);
              next(null, chunk);
            });
        },
      })
    )
    .pipe(dest(path)); // 输出到某文件中
}

// 编译 TS
compileTS(false);
compileTS();

// 编译 CSS
compileCSS(false);
compileCSS();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

运行 node complie.js,编译完后打开 http://localhost:3000/ 会发现浏览器控制台会发出 Uncaught Error: Cannot find module './style/element.scss' 错误。意思就是没有 lib/style/element.scsses/style/element.scss 文件,可我们期望引入的是编译好的 lib/style/element.csses/style/element.css,并且这两个文件当前是存在的。

之所以会出现上述情况,是因为我们将 .ts.js.scss.css。但是 .ts 里面的文件路径是不会进行调整的。例如:原本 .ts 文件引入的是 xx/xx.scss,那么转化成 .js 的内容还是 xx/xx.scss。可以打开编译产物 lib/element.jses/element.js 进行查看:





























 






// lib/element.js
"use strict";

var _typeof = require("@babel/runtime-corejs3/helpers/typeof");

var _WeakMap = require("@babel/runtime-corejs3/core-js-stable/weak-map");

var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property");

var _Object$getOwnPropertyDescriptor = require("@babel/runtime-corejs3/core-js-stable/object/get-own-property-descriptor");

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

_Object$defineProperty(exports, "__esModule", {
  value: true,
});

exports.createBackgroudImg = createBackgroudImg;
exports.createRandomTextElement = createRandomTextElement;

var _regenerator = _interopRequireDefault(
  require("@babel/runtime-corejs3/regenerator")
);

var _asyncToGenerator2 = _interopRequireDefault(
  require("@babel/runtime-corejs3/helpers/asyncToGenerator")
);

require("./style/element.scss");

var util = _interopRequireWildcard(require("./util"));

// 以下文件内容省略
// ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34




 



// es/element.js
import _asyncToGenerator from "@babel/runtime-corejs3/helpers/asyncToGenerator";
import _regeneratorRuntime from "@babel/runtime-corejs3/regenerator";

import "./style/element.scss";

import * as util from "./util"; // 使用 background 显示图片
1
2
3
4
5
6
7

我们需要将原本引入的 .scss 改为 .css,例如:require("./style/element.scss"); 字符串调整为 require("./style/element.css");。可以在 .ts.js 的过程使用正则表达式对 style/xxx.scss 字符串进行全局匹配替换。调整 compile.js 实现前面描述的逻辑:



































 
 
 
 
 

























var stream = require("stream");
var { src, dest } = require("gulp");
var babel = require("@babel/core");
var postcss = require("postcss");
var sass = require("sass");

function compileTS(modules) {
  // babel 配置
  var config = {
    presets: [
      ["@babel/preset-env", { modules, debug: true, targets: "IE >= 11" }],
      "@babel/preset-typescript",
    ],
    plugins: [["@babel/plugin-transform-runtime", { corejs: 3 }]],
  };

  // Commonjs 输出至 ./lib
  // ES6 Module 输出至 ./es
  var path = modules === false ? "./es" : "./lib";

  // 读取文件
  src("./src/**/*.ts")
    .pipe(
      // 可以使用 through2 库,会更方便
      // 创建转化流,类似于双工流,但其输出是其输入的转换的转换流。
      new stream.Transform({
        objectMode: true,
        transform: function(chunk, encoding, next) {
          // 转化逻辑
          babel.transform(
            chunk.contents.toString(encoding), // 文件内容
            // 需要额外添加 filename
            { ...config, filename: chunk.basename },
            (err, res) => {
              // 文件中的 style/xxx.scss -> style/xxx.css
              const content = res.code.replace(
                /([\\/]style[\\/](?:.+)).scss/g,
                "$1.css"
              );
              // 文件内容修改成转化后的代码
              chunk.contents = Buffer.from(content);
              // 后缀名文件 .ts -> .js
              chunk.extname = ".js";
              next(null, chunk);
            }
          );
        },
      })
    )
    .pipe(dest(path)); // 输出到某文件中
}

function compileCSS(modules) {
  // ...
}

// 编译 TS
compileTS(false);
compileTS();

// 编译 CSS
compileCSS(false);
compileCSS();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

重新运行 node compile.js,编译完后在浏览器打开 http://localhost:3000/ ,结果正常运行。也可以查看编译产物 lib/style/element.jses/style/element.js 文件是否对引入的 .scss 路径进行替换。