日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

未能加载文件或程序集或它的某一个依赖项_手写一个miniwebpack

發(fā)布時(shí)間:2024/1/23 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 未能加载文件或程序集或它的某一个依赖项_手写一个miniwebpack 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

之前好友希望能介紹一下 webapck 相關(guān)的內(nèi)容,所以最近花費(fèi)了兩個(gè)多月的準(zhǔn)備,終于完成了 webapck 系列,它包括一下幾部分:

  • webapck 系列一:手寫一個(gè) JavaScript 打包器

  • webpack 系列二:所有配置項(xiàng)

  • webpack 系列三:優(yōu)化 90% 打包速度

  • webpack 系列四:優(yōu)化包體積

  • webapck 系列五:優(yōu)化首屏加載時(shí)間與頁(yè)面流暢度

  • webapck 系列六:構(gòu)建包分析

  • webapck 系列七:詳細(xì)配置

  • webapck 系列八:手寫一個(gè) webapck 插件(模擬 HtmlWebpackPlugin 的實(shí)現(xiàn))

  • webapck 系列九:webapck4 核心源碼解讀

  • webapck 系列十:webapck5 展望

所有的內(nèi)容之后會(huì)陸續(xù)放出,如果你有任何想要了解的內(nèi)容或者有任何疑問,可以公眾號(hào)后臺(tái)留言提問。

作為一個(gè)前端開發(fā)人員,我們花費(fèi)大量的時(shí)間去處理 webpack、gulp 等打包工具,將高級(jí) JavaScript 項(xiàng)目打包成更復(fù)雜、更難以解讀的文件包,運(yùn)行在瀏覽器中,那么理解 JavaScript 打包機(jī)制就很必要,它幫助你更好的調(diào)試項(xiàng)目、更快的定位問題產(chǎn)生的問題,并且?guī)椭愀玫睦斫狻⑹褂?webpack 等打包工具。
在這章你將會(huì)深入理解 JavaScript 打包器是什么,它的打包機(jī)制是什么?解決了什么問題?如果你理解了這些,接下來的 webpack 優(yōu)化就會(huì)很簡(jiǎn)單。

一、什么是模塊

一個(gè)模塊可以有很多定義,但我認(rèn)為:模塊是一組與特定功能相關(guān)的代碼。它封裝了實(shí)現(xiàn)細(xì)節(jié),公開了一個(gè)公共API,并與其他模塊結(jié)合以構(gòu)建更大的應(yīng)用程序。

所謂模塊化,就是為了實(shí)現(xiàn)更高級(jí)別的抽象,它將一類或多種實(shí)現(xiàn)封裝到一個(gè)模塊中,我們不必考慮模塊內(nèi)是怎樣的依賴關(guān)系,僅僅調(diào)用它暴露出來的 API 即可。

例如在一個(gè)項(xiàng)目中:

<html>
??<script?src="/src/man.js">script>
??<script?src="/src/person.js">script>
html>

其中?person.js?中依賴?man.js?,在引用時(shí)如果你把它們的引用順序顛倒就會(huì)報(bào)錯(cuò)。在大型項(xiàng)目中,這種依賴關(guān)系就顯得尤其重要,而且極難維護(hù),除此之外,它還有以下問題:

  • 一切都加載到全局上下文中,導(dǎo)致名稱沖突和覆蓋

  • 涉及開發(fā)人員的大量手動(dòng)工作,以找出依賴關(guān)系和包含順序

所以,模塊就尤其重要。

由于前后端 JavaScript 分別擱置在 HTTP 的兩端,它們扮演的角色不同,側(cè)重點(diǎn)也不一樣。?瀏覽器端的 JavaScript 需要經(jīng)歷從一個(gè)服務(wù)器端分發(fā)到多個(gè)客戶端執(zhí)行,而服務(wù)器端 JS 則是相同的代碼需要多次執(zhí)行。前者的瓶頸在于寬帶,后者的瓶頸則在于 CPU 等內(nèi)存資源。前者需要通過網(wǎng)絡(luò)加載代碼,后者則需要從磁盤中加載,?兩者的加載速度也不是在一個(gè)數(shù)量級(jí)上的。所以前后端的模塊定義不是一致的,其中服務(wù)器端的模塊定義為:

  • CJS(CommonJS):旨在用于服務(wù)器端 JavaScript 的同步定義,Node 的模塊系統(tǒng)實(shí)際上基于 CJS;

但 CommonJS 是以同步方式導(dǎo)入,因?yàn)橛糜诜?wù)端,文件都在本地,同步導(dǎo)入即使卡住主線程影響也不大,但在瀏覽器端,如果在 UI 加載的過程中需要花費(fèi)很多時(shí)間來等待腳本加載完成,這會(huì)造成用戶體驗(yàn)的很大問題。鑒于網(wǎng)絡(luò)的原因, CommonJS 為后端 JavaScript 制定的規(guī)范并不完全適合與前端的應(yīng)用場(chǎng)景,下面來介紹 JavaScript 前端的規(guī)范。

  • AMD(異步模塊定義):被定義為用于瀏覽器中模塊的異步模型,RequireJS 是 AMD 最受歡迎的實(shí)現(xiàn);

  • UMD(通用模塊定義):它本質(zhì)上一段 JavaScript 代碼,放置在庫(kù)的頂部,可讓任何加載程序、任何環(huán)境加載它們;

  • ES2015(ES6):定義了異步導(dǎo)入和導(dǎo)出模塊的語(yǔ)義,會(huì)編譯成?require/exports?來執(zhí)行的,這也是我們現(xiàn)今最常用的模塊定義;

二、什么是打包器

所謂打包器,就是前端開發(fā)人員用來將 JavaScript 模塊打包到一個(gè)可以在瀏覽器中運(yùn)行的優(yōu)化的 JavaScript 文件的工具,例如 webapck、rollup、gulp 等。

舉個(gè)例子,你在一個(gè) html 文件中引入多個(gè) JavaScript 文件:

<html>
??<script?src="/src/entry.js">script>
??<script?src="/src/message.js">script>
??<script?src="/src/hello.js">script>
??<script?src="/src/name.js">script>
html>

當(dāng)瀏覽器打開該網(wǎng)頁(yè)時(shí),每個(gè) js 文件都需要一個(gè)單獨(dú)的 http 請(qǐng)求,即 4 個(gè)往返請(qǐng)求,才能正確的啟動(dòng)你的項(xiàng)目。

我們知道瀏覽器加載模塊很慢,即使是 HTTP/2 支持有效的加載許多小文件,但其性能都不如加載一個(gè)更加有效(即使不做任何優(yōu)化)。

因此,最好將所有 4 個(gè)文件合并為1個(gè):

<html>
??<script?src="/dist/bundle.js">script>
html>

這樣只需要一次 http 請(qǐng)求即可。

如何打包到一個(gè)文件喃?它通常有一個(gè)入口文件,從入口文件開始,獲取所有的依賴項(xiàng),并打包到一個(gè)文件?bundle.js?中。例如上例,我們可以以?/src/entry.js?作為入口文件,進(jìn)行合并其余的 3 個(gè) JavaScript 文件。

當(dāng)然合并不能是簡(jiǎn)單的將 4 個(gè)文件所有內(nèi)容放入一個(gè)?bundle.js?中。我們先思考一下,它具體該怎么實(shí)現(xiàn)喃?

1. 解析入口文件,獲取所有的依賴項(xiàng)

首先我們唯一確定的是入口文件的地址,通過入口文件的地址可以

  • 獲取其文件內(nèi)容

  • 獲取其依賴模塊的相對(duì)地址

由于依賴模塊的引入是通過相對(duì)路徑(import './message.js'),所以,我們需要保存入口文件的路徑,結(jié)合依賴模塊的相對(duì)地址,就可以確定依賴模塊絕對(duì)地址,讀取它的內(nèi)容。

如何在依賴關(guān)系中去表示一個(gè)模塊,以方便在依賴圖中引用

所以我們可以模塊表示為:

  • code: 文件解析內(nèi)容,注意解析后代碼能夠在當(dāng)前以及舊瀏覽器或環(huán)境中運(yùn)行;

  • dependencies: 依賴數(shù)組,為所有依賴模塊路徑(相對(duì))路徑;

  • filename: 文件絕對(duì)路徑,當(dāng)?import?依賴模塊為相對(duì)路徑,結(jié)合當(dāng)前絕對(duì)路徑,獲取依賴模塊路徑;

其中 filename(絕對(duì)路徑) 可以作為每個(gè)模塊的唯一標(biāo)識(shí)符,通過 key: value 形式,直接獲取文件的內(nèi)容一依賴模塊:

//?模塊
'src/entry':?{
??code:?'',?//?文件解析后內(nèi)容
??dependencies:?["./message.js"],?//?依賴項(xiàng)
}

2. 遞歸解析所有的依賴項(xiàng),生成一個(gè)依賴關(guān)系圖

我們已經(jīng)確定了模塊的表示,那怎么才能將這所有的模塊關(guān)聯(lián)起來,生成一個(gè)依賴關(guān)系圖,通過這個(gè)依賴關(guān)系可以直接獲取所有模塊的依賴模塊、依賴模塊的代碼、依賴模塊的來源、依賴模塊的依賴模塊。

如何去維護(hù)依賴文件間的關(guān)系

現(xiàn)在對(duì)于每一個(gè)模塊,可以唯一表示的就是?filename?,而我們?cè)谟扇肟谖募f歸解析時(shí),我們可以獲取到每個(gè)文件的依賴數(shù)組?dependencies?,也就是每個(gè)依賴項(xiàng)的相對(duì)路徑,所以我們需要定義一個(gè):

//?關(guān)聯(lián)關(guān)系
let?mapping?=?{}

用來在運(yùn)行代碼時(shí),由?import?相對(duì)路徑映射到?import?絕對(duì)路徑。

所以我們模塊可以定義為[filename: {}]:

//?模塊
'src/entry':?{
??code:?'',?//?文件解析后內(nèi)容
??dependencies:?["./message.js"],?//?依賴項(xiàng)
??mapping:{
????"./message.js":?"src/message.js"???????
??}
}

則依賴關(guān)系圖為:

//?graph?依賴關(guān)系圖
let?graph?=?{
??//?entry?模塊
??"src/entry.js":?{
????code:?'',
????dependencies:?["./src/message.js"],
????mapping:{
??????"./message.js":?"src/message.js"???????
????}
??},
??//?message?模塊
??"src/message.js":?{
????code:?'',
????dependencies:?[],
????mapping:{},
??}
}

當(dāng)項(xiàng)目運(yùn)行時(shí),通過入口文件成功獲取入口文件代碼內(nèi)容,運(yùn)行其代碼,當(dāng)遇到?import?依賴模塊時(shí),通過?mapping?映射其為絕對(duì)路徑,就可以成功讀取模塊內(nèi)容。

并且每個(gè)模塊的絕對(duì)路徑 filename 是唯一的,當(dāng)我們將模塊接入到依賴圖?graph?時(shí),僅僅需要判斷?graph[filename]?是否存在,如果存在就不需要二次加入,剔除掉了模塊的重復(fù)打包。

3. 使用依賴圖,返回一個(gè)可以在瀏覽器運(yùn)行的 JavaScript 文件

現(xiàn)今,可立即執(zhí)行的代碼形式,最流行的就是 IIFE(立即執(zhí)行函數(shù)),它同時(shí)能夠解決全局變量污染的問題。

IIFE

所謂 IIFE,就是在聲明市被直接調(diào)用的匿名函數(shù),由于 JavaScript 變量的作用域僅限于函數(shù)內(nèi)部,所以你不必考慮它會(huì)污染全局變量。

(function(man){
??function?log(name)?{
????console.log(`hello?${name}`);
??}
??log(man.name)
})({name:?'bottle'});
//?hello?bottle

4. 輸出到 dist/bundle.js

fs.writeFile?寫入?dist/bundle.js?即可。

至此,打包流程與實(shí)現(xiàn)方案已確定,接下來就實(shí)踐一遍吧!

三、創(chuàng)建一個(gè)minipack項(xiàng)目

新建一個(gè) minipack 文件夾,并?npm init?,創(chuàng)建以下文件:

-?src
-?-?entry.js?//?入口?js
-?-?message.js?//?依賴項(xiàng)
-?-?hello.js?//?依賴項(xiàng)
-?-?name.js?//?依賴項(xiàng)
-?index.js?//?打包?js
-?minipack.config.js?//?minipack?打包配置文件
-?package.json?
-?.gitignore

其中?entry.js?:

import?message?from?'./message.js'
import?{name}?from?'./name.js'

message()
console.log('----name-----:?',?name)

message.js?:

import?{hello}?from?'./hello.js'
import?{name}?from?'./name.js'

export?default?function?message()?{
??console.log(`${hello}?${name}!`)
}

hello.js?:

export?const?hello?=?'hello'

name.js?:

export?const?name?=?'bottle'

minipack.config.js?:

const?path?=?require('path')
module.exports?=?{
????entry:?'src/entry.js',
????output:?{
????????filename:?"bundle.js",
????????path:?path.resolve(__dirname,?'./dist'),
????}
}

并安裝文件

npm?install?@babel/core?@babel/parser?@babel/preset-env?@babel/traverse?--save-dev

至此,整個(gè)項(xiàng)目創(chuàng)建完成。接下來就是打包了:

  • 解析入口文件,遍歷所有依賴項(xiàng)

  • 遞歸解析所有的依賴項(xiàng),生成一個(gè)依賴關(guān)系圖

  • 使用依賴圖,返回一個(gè)可以在瀏覽器運(yùn)行的 JavaScript 文件

  • 輸出到 ?/dist/bundle.js

四、解析入口文件,遍歷所有依賴項(xiàng)

1. @babel/parser 解析入口文件,獲取 AST

在 ./index.js 文件中,我們創(chuàng)建一個(gè)打包器,首先解析入口文件,我們使用?@babel/parser解析器進(jìn)行解析:

步驟一:讀取入口文件內(nèi)容

//?獲取配置文件
const?config?=?require('./minipack.config');
//?入口
const?entry?=?config.entry;
const?content?=?fs.readFileSync(entry,?'utf-8');

步驟二:使用?@babel/parser(JavaScript解析器)解析代碼,生成 ast(抽象語(yǔ)法樹)

const?babelParser?=?require('@babel/parser')
const?ast?=?babelParser.parse(content,?{
??sourceType:?"module"
})

其中,sourceType?指示代碼應(yīng)解析的模式。可以是"script",?"module"或?"unambiguous"?之一,其中 ?"unambiguous"?是讓?@babel/parser?去猜測(cè),如果使用 ES6?import?或?export?的話就是?"module"?,否則為?"script"?。這里使用 ES6?import?或?export?,所以就是?"module"?。

由于 ast 樹較復(fù)雜,所以這里我們可以通過 https://astexplorer.net/ 查看:

我們已經(jīng)獲取了入口文件所有的 ast,接下來我們要做什么喃?

  • 解析 ast,解析入口文件內(nèi)容(可在當(dāng)前和舊瀏覽器或環(huán)境中向后兼容的 JavaScript 版本)

  • 獲取它所有的依賴模塊?dependencies

2. 獲取入口文件內(nèi)容

我們已經(jīng)知道了入口文件的 ast,可以通過?@babel/core?的?transformFromAst?方法,來解析入口文件內(nèi)容:

const?{transformFromAst}?=?require('@babel/core');
const?{code}?=?transformFromAst(ast,?null,?{
??presets:?['@babel/preset-env'],
})

3. 獲取它所有的依賴模塊

就需要通過 ast 獲取所有的依賴模塊,也就是我們需要獲取 ast 中所有的?node.source.value?,也就是?import?模塊的相對(duì)路徑,通過這個(gè)相對(duì)路徑可以尋找到依賴模塊。

步驟一:定義一個(gè)依賴數(shù)組,用來存放 ast 中解析出的所有依賴

const?dependencies?=?[]

步驟二:使用?@babel/traverse?,它和 babel 解析器配合使用,可以用來遍歷及更新每一個(gè)子節(jié)點(diǎn)

traverse?函數(shù)是一個(gè)遍歷?AST?的方法,由?babel-traverse?提供,他的遍歷模式是經(jīng)典的?visitor?模式 ,visitor?模式就是定義一系列的?visitor?,當(dāng)碰到?AST?的?type === visitor?名字時(shí),就會(huì)進(jìn)入這個(gè)?visitor?的函數(shù)。類型為?ImportDeclaration?的 AST 節(jié)點(diǎn),其實(shí)就是我們的?import xxx from xxxx,最后將地址?push?到?dependencies?中.

const?traverse?=?require('@babel/traverse').default
traverse(ast,?{
??//?遍歷所有的?import?模塊,并將相對(duì)路徑放入?dependencies
??ImportDeclaration:?({node})?=>?{
????dependencies.push(node.source.value)
??}
})

3. 有效返回

{
??dependencies,
??code,
}

完整代碼:

/**
?*?解析文件內(nèi)容及其依賴,
?*?期望返回:
?*??????dependencies:?文件依賴模塊
?*??????code:?文件解析內(nèi)容?
?*?@param?{string}?filename?文件路徑
?*/
function?createAsset(filename)?{
??//?讀取文件內(nèi)容
??const?content?=?fs.readFileSync(filename,?'utf-8')
??//?使用?@babel/parser(JavaScript解析器)解析代碼,生成?ast(抽象語(yǔ)法樹)
??const?ast?=?babelParser.parse(content,?{
????sourceType:?"module"
??})

??//?從?ast?中獲取所有依賴模塊(import),并放入?dependencies?中
??const?dependencies?=?[]
??traverse(ast,?{
????//?遍歷所有的?import?模塊,并將相對(duì)路徑放入?dependencies
????ImportDeclaration:?({
??????node
????})?=>?{
??????dependencies.push(node.source.value)
????}
??})
??//?獲取文件內(nèi)容
??const?{
????code
??}?=?transformFromAst(ast,?null,?{
????presets:?['@babel/preset-env'],
??})
??//?返回結(jié)果
??return?{
????dependencies,
????code,
??}
}

五、遞歸解析所有的依賴項(xiàng),生成一個(gè)依賴關(guān)系圖

步驟一:獲取入口文件:

const?mainAssert?=?createAsset(entry)

步驟二:創(chuàng)建依賴關(guān)系圖:

由于每個(gè)模塊都是 key: value 形式,所以定義依賴圖為:

//?entry:?入口文件絕對(duì)地址
const?graph?=?{
??[entry]:?mainAssert
}

步驟三:遞歸搜索所有的依賴模塊,加入到依賴關(guān)系圖中:

定義一個(gè)遞歸搜索函數(shù):

/**
?*?遞歸遍歷,獲取所有的依賴
?*?@param?{*}?assert?入口文件
*/
function?recursionDep(filename,?assert)?{
??//?跟蹤所有依賴文件(模塊唯一標(biāo)識(shí)符)
??assert.mapping?=?{}
??//?由于所有依賴模塊的?import?路徑為相對(duì)路徑,所以獲取當(dāng)前絕對(duì)路徑
??const?dirname?=?path.dirname(filename)
??assert.dependencies.forEach(relativePath?=>?{
????//?獲取絕對(duì)路徑,以便于?createAsset?讀取文件
????const?absolutePath?=?path.join(dirname,?relativePath)
????//?與當(dāng)前?assert?關(guān)聯(lián)
????assert.mapping[relativePath]?=?absolutePath
????//?依賴文件沒有加入到依賴圖中,才讓其加入,避免模塊重復(fù)打包
????if?(!queue[absolutePath])?{
??????//?獲取依賴模塊內(nèi)容
??????const?child?=?createAsset(absolutePath)
??????//?將依賴放入?queue,以便于繼續(xù)調(diào)用?recursionDep?解析依賴資源的依賴,
??????//?直到所有依賴解析完成,這就構(gòu)成了一個(gè)從入口文件開始的依賴圖
??????queue[absolutePath]?=?child
??????if(child.dependencies.length?>?0)?{
????????//?繼續(xù)遞歸
????????recursionDep(absolutePath,?child)
??????}
????}
??})
}

從入口文件開始遞歸:

//?遍歷?queue,獲取每一個(gè)?asset?及其所以依賴模塊并將其加入到隊(duì)列中,直至所有依賴模塊遍歷完成
for?(let?filename?in?queue)?{
??let?assert?=?queue[filename]
??recursionDep(filename,?assert)
}

六、使用依賴圖,返回一個(gè)可以在瀏覽器運(yùn)行的 JavaScript 文件

步驟一:創(chuàng)建一個(gè)了立即執(zhí)行函數(shù),用于在瀏覽器上直接運(yùn)行

const?result?=?`
??(function()?{
??})()
`

步驟二:將依賴關(guān)系圖作為參數(shù)傳遞給立即執(zhí)行函數(shù)

定義傳遞參數(shù) modules:

let?modules?=?''

遍歷?graph,將每個(gè)?mod?以?key: value,?的方式加入到?modules,

注意:由于依賴關(guān)系圖要傳入以上立即執(zhí)行函數(shù)中,然后寫入到?dist/bundle.js?運(yùn)行,所以,code?需要放在?function(require, module, exports){${mod.code}}?中,避免污染全局變量或其它模塊

for?(let?filename?in?graph)?{
??let?mod?=?graph[filename]
??modules?+=?`'${filename}':?[
????function(require,?module,?exports)?{${mod.code}
????},${JSON.stringify(mod.mapping)},
??],`
}

步驟三:將參數(shù)傳入立即執(zhí)行函數(shù),并立即執(zhí)行入口文件:

首先實(shí)現(xiàn)一個(gè) require 函數(shù),require('${entry}')?執(zhí)行入口文件,entry?為入口文件絕對(duì)路徑,也為模塊唯一標(biāo)識(shí)符

const?result?=?`
??(function(modules)?{
????require('${entry}')
??})({${modules}})
`

注意:modules?是一組?key: value,,所以我們將它放入?{}?中

步驟四:重寫瀏覽器?require?方法,當(dāng)代碼運(yùn)行?require('./message.js')?轉(zhuǎn)換成?require(src/message.js)

const?result?=?`
??(function(modules)?{
????function?require(moduleId)?{
??????const?[fn,?mapping]?=?modules[moduleId]
??????function?localRequire(name)?{
????????return?require(mapping[name])
??????}
??????const?module?=?{exports:?{}}
??????fn(localRequire,?module,?module.exports)
??????return?module.exports
????}
????require('${entry}')
??})({${modules}})
`

注意:

  • moduleId?為傳入的?filename?,為模塊的唯一標(biāo)識(shí)符

  • 通過解構(gòu)?const [fn, mapping] = modules[id]?來獲得我們的函數(shù)包裝(function(require, module, exports) {${mod.code}})和?mappings?對(duì)象

  • 由于一般情況下?require?都是?require?相對(duì)路徑,而不是絕對(duì)路徑,所以重寫?fn的?require?方法,將?require?相對(duì)路徑轉(zhuǎn)換成?require?絕對(duì)路徑,即?localRequire?函數(shù)

  • 將?module.exports?傳入到?fn?中,將依賴模塊內(nèi)容需要輸出給其它模塊使用時(shí),當(dāng)?require?某一依賴模塊時(shí),就可以直接通過?module.exports?將結(jié)果返回

七、輸出到 dist/bundle.js

//?打包
const?result?=?bundle(graph)
//?寫入?./dist/bundle.js
fs.writeFile(`${output.path}/${output.filename}`,?result,?(err)?=>?{
??if?(err)?throw?err;
??console.log('文件已被保存');
})

八、總結(jié)及源碼

本來想簡(jiǎn)單的寫寫,結(jié)果修修改改又那么多??♀???♀???♀?,但總要吃透才好。

源碼地址:https://github.com/sisterAn/minipack

超強(qiáng)干貨來襲 云風(fēng)專訪:近40年碼齡,通宵達(dá)旦的技術(shù)人生

總結(jié)

以上是生活随笔為你收集整理的未能加载文件或程序集或它的某一个依赖项_手写一个miniwebpack的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。