構建工具是如何用 node 操作 html/js/css/md 檔案的
從本質上來說,html/js/css/md ...
原始碼檔案都是文字檔案,文字檔案的內容都是字串,對文字檔案的操作其實就是對字串的操作。
操作原始碼的方式又主要分成兩種:
- 當作字串,進行增、刪、改等操作
- 按照某種語法、規則,把字串讀取成一個物件,然後對這個物件進行操作,最後匯出新的字串
1. 操作 html
檔案
html
的語法比較簡單,並且一般操作 html
都是插入、替換、模板引擎渲染等在字串上的操作,所以使用第一種方式的比較多。
比如:
- html-loader
- html-webpack-plugin
- html-minifier
- handlebars 模板引擎
- pug 模板引擎
- ejs 模板引擎
一般以第二種方式來操作 html
的都是將 html
文字解析成 dom
樹物件,然後進行 dom
操作,最後再匯出成新的程式碼文字。
比如:
以 cheerio
為例,操作 html
文字:
cheerio
能夠載入一個 html
文字,例項化一個類 jQuery
物件,然後使用 jQuery
的 api
像操作 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
組織制定的 DOM
和 HTML
標準。
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-loader、autoprefixer、cssnano 等的底層都是使用的
postcss
來解析 - rework、reworkcss: 抽象語法樹解析器
-
csstree: 比如 csso 的底層就是使用
csstree
來解析
以 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
文字的目的有兩個:
- 作為編輯器編輯
markdown
文字,或作為渲染器渲染markdown
文字為html
文字 - 從
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
版權宣告:自由轉載-非商用-非衍生-保持署名(創意共享3.0許可證)