1. 程式人生 > >構建工具是如何用 node 操作 html/js/css/md 檔案的

構建工具是如何用 node 操作 html/js/css/md 檔案的

構建工具是如何用 node 操作 html/js/css/md 檔案的

從本質上來說,html/js/css/md ... 原始碼檔案都是文字檔案,文字檔案的內容都是字串,對文字檔案的操作其實就是對字串的操作。

操作原始碼的方式又主要分成兩種:

  1. 當作字串,進行增、刪、改等操作
  2. 按照某種語法、規則,把字串讀取成一個物件,然後對這個物件進行操作,最後匯出新的字串

1. 操作 html 檔案

html 的語法比較簡單,並且一般操作 html 都是插入、替換、模板引擎渲染等在字串上的操作,所以使用第一種方式的比較多。

比如:

一般以第二種方式來操作 html 的都是將 html 文字解析成 dom 樹物件,然後進行 dom 操作,最後再匯出成新的程式碼文字。

比如:

cheerio 為例,操作 html 文字:

cheerio 能夠載入一個 html 文字,例項化一個類 jQuery 物件,然後使用 jQueryapi 像操作 dom 一樣操作這段文字,最後匯出新的 html 文字。


const cheerio = require('cheerio');
const $ = cheerio.load('<h2 class="title">Hello world</h2>'); // 載入一個 html 文字

$('h2.title').text('Hello there!');
$('h2').addClass('welcome');

$.html(); // 匯出新的 html 文字
//=> <h2 class="title welcome">Hello there!</h2>

jsdom 為例,操作 html 文字:

jsdom 是用 js 將一個 html 文字解析為一個 dom 物件,並實現了一系列 web 標準,特別是 WHATWG 組織制定的 DOMHTML 標準。


const jsdom = require("jsdom");
const { JSDOM } = jsdom;

const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
console.log(dom.window.document.querySelector("p").textContent); // "Hello world"

2. 操作 js 檔案

因為 js 語法比較複雜,僅僅是如字串一樣進行增刪改,只能做一些小的操作,意義不大。所以,一般操作 js 檔案都是採用的第二種方式。

在第二種方式中,一般是工具將 js 文字解析成抽象語法樹(AST,Abstract Syntax Tree抽象語法樹),然後對這棵語法樹以面向物件的方式做增刪改等操作,最後再匯出成新的程式碼文字。

生成抽象語法樹的工具主要有:

acorn 為例,將 1 + 1 片段進行解析:


const acorn = require('acorn');

const tree = acorn.parse('1 + 1');

// tree 的 json 化表示
{
  type: 'Program',
  start: 0,
  end: 5,
  body: [{
    type: 'ExpressionStatement',
    start: 0,
    end: 5,
    expression: {
      type: 'BinaryExpression',
      start: 0,
      end: 5,
      left: { type: 'Literal', start: 0, end: 1, value: 1, raw: '1' },
      operator: '+',
      right: { type: 'Literal', start: 4, end: 5, value: 1, raw: '1' } 
    }
  }],
  sourceType: 'script' 
}

babel-parser 為例,將 1 + 1 片段進行解析:


const parser = require('@babel/parser');

const tree = parser.parse('1 + 1');

// tree 的 json 化表示
{
  type: 'File',
  start: 0,
  end: 5,
  loc: {
    start: { line: 1, column: 0 },
    end: { line: 1, column: 5 } 
  },
  program: {
    type: 'Program',
    start: 0,
    end: 5,
    loc: {
      start: { line: 1, column: 0 },
      end: { line: 1, column: 5 } 
    },
    sourceType: 'script',
    interpreter: null,
    body: [{
      type: 'ExpressionStatement',
      start: 0,
      end: 5,
      loc: {
        start: { line: 1, column: 0 },
        end: { line: 1, column: 5 } 
      },
      expression: {
        type: 'BinaryExpression',
        start: 0,
        end: 5,
        loc: {
          start: { line: 1, column: 0 },
          end: { line: 1, column: 5 } 
        },
        left: {
          type: 'NumericLiteral',
          start: 0,
          end: 1,
          loc: {
            start: { line: 1, column: 0 },
            end: { line: 1, column: 5 } 
          },
          extra: { rawValue: 1, raw: '1' },
          value: 1        
        },
        operator: '+',
        right: {
          type: 'NumericLiteral',
          start: 4,
          end: 5,
          loc: {
            start: { line: 1, column: 0 },
            end: { line: 1, column: 5 } 
          },
          extra: { rawValue: 1, raw: '1' },
          value: 1 
        } 
      } 
    }],
    directives: [] 
  },
  comments: [] 
}

3. 操作 css 檔案

css 的語法比 html 要複雜一些,一些簡單的操作如插入、替換,可以用直接以字串的方式操作,但如果是壓縮、auto prefix、css-modules 等複雜的功能時,就需要用第二種方式操作 css 了。

在第二種方式中,一般也是將 css 文字解析成一棵抽象語法樹,然後進行操作。

比如:

postcss 為例,操作 css 文字:


const autoprefixer = require('autoprefixer');
const postcss = require('postcss');
const precss = require('precss');

const css = `
.hello {
  display: flex;
  color: red;
  backgroundColor: #ffffff;
}
`;

postcss([precss, autoprefixer({browsers: ['last 2 versions', '> 5%']})])
  .process(css)
  .then(result => {
    console.log(result.css);
  });

輸出的文字:


.hello {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  color: red;
  backgroundColor: #ffffff;
}

rework 為例,操作 css 文字:


const css = require('css');
const ast = css.parse('body { font-size: 12px; }');

console.log(css.stringify(ast));

輸出的文字:


body {
  font-size: 12px;
}

4. 操作 markdown/md 檔案

一般來說,操作 markdown 文字的目的有兩個:

  1. 作為編輯器編輯 markdown 文字,或作為渲染器渲染 markdown 文字為 html 文字
  2. markdown 文字中讀取資訊、校驗嵌入的原始碼、優化格式等

所以,儘管 markdown 的語法也很簡單,但一般並不會直接去使用字串的方式去操作 markdown 文字,一般都是使用的第二種方式。

比如:

  • markdown-it: 作為編輯器或渲染器的好手
  • remark: 構建抽象語法樹進行操作的好手

markdown-it 為例,操作 markdown 文字:


const md = require('markdown-it')();
const result = md.render('# markdown-it rulezz!');

console.log(result);

輸出的文字:


<h1>markdown-it rulezz!</h1>

remark 為例,操作 markdown 文字:


const remark = require('remark')
const recommended = require('remark-preset-lint-recommended')
const html = require('remark-html')
const report = require('vfile-reporter')

remark()
  .use(recommended)
  .use(html)
  .process('## Hello world!', function(err, file) {
    console.error(report(err || file))
    console.log(String(file))
  })

校驗錯誤提示:


1:1  warning  Missing newline character at end of file  final-newline  remark-lint

⚠ 1 warning

輸出的文字:


<h2>Hello world!</h2>

後續

更多部落格,檢視 https://github.com/senntyou/blogs

作者:深予之 (@senntyou)

版權宣告:自由轉載-非商用-非衍生-保持署名(創意共享3.0許可證