<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Webpack入門(mén)到精通(AST、Babel、依賴(lài))

          共 10466字,需瀏覽 21分鐘

           ·

          2021-09-12 01:09

          ?

          讀者投稿:作者:一咻,原文地址:https://juejin.cn/post/6975885302493609991?share_token=68a0b777-70c1-4021-a894-3ed9f8c107e9

          ?

          babel與AST

          初始化項(xiàng)目

          mkdir webpack-study
          cd webpack-study
          yarn init -y

          此時(shí)在package.json里面加入以下依賴(lài)

          {
            "name""01",
            "version""1.0.0",
            "main""index.js",
            "license""MIT",
            "dependencies": {
              "@babel/core""7.12.3",
              "@babel/generator""7.12.5",
              "@babel/parser""7.12.5",
              "@babel/preset-env""7.12.1",
              "@babel/traverse""7.12.5",
              "ts-node""9.0.0",
              "typescript""4.0.5"
            },
            "devDependencies": {
              "@types/babel__core""7.1.12",
              "@types/babel__generator""7.6.2",
              "@types/babel__parser""7.1.1",
              "@types/babel__preset-env""7.9.1",
              "@types/babel__traverse""7.0.15",
              "@types/node""14.14.6"
            }
          }

          babel以及項(xiàng)目依賴(lài)

          對(duì)使用到的包進(jìn)行說(shuō)明 詳細(xì)內(nèi)容請(qǐng)參考: 理解babel的基本原理和使用方法

          • @babel/core Babel 是一個(gè) JavaScript 編譯器, Babel 是一個(gè)工具鏈,主要用于將采用 ECMAScript 2015+ 語(yǔ)法編寫(xiě)的代碼轉(zhuǎn)換為向后兼容的 JavaScript 語(yǔ)法,以便能夠運(yùn)行在當(dāng)前和舊版本的瀏覽器或其他環(huán)境中。下面列出的是 Babel 能為你做的事情:
          1. 語(yǔ)法轉(zhuǎn)換
          2. 通過(guò) Polyfill 方式在目標(biāo)環(huán)境中添加缺失的特性(通過(guò)第三方 polyfill 模塊,例如 core-js,實(shí)現(xiàn))
          3. 源碼轉(zhuǎn)換 (codemods)
          • @babel/generator

            這個(gè)過(guò)程已經(jīng)在上面的實(shí)例中有所展現(xiàn),使用的插件是@babel/generator,其作用就是將轉(zhuǎn)換好的ast重新生成代碼。這樣的代碼就就可以安全的在瀏覽器運(yùn)行。

          • @babel/parser

            在babel中編譯器插件是@babel/parser,其作用就是將源碼轉(zhuǎn)換為AST,

          • @babel/preset-env (預(yù)設(shè)(preset)——babel的插件套裝)

            那么問(wèn)題來(lái)了新語(yǔ)法新特性那么多,難道我們要挨個(gè)去加嗎?當(dāng)然不是,babel已經(jīng)預(yù)設(shè)了幾套插件,將最新的語(yǔ)法進(jìn)行轉(zhuǎn)換,可以使用在不同的環(huán)境中,如下:

          @babel/preset-env
          @babel/preset-flow
          @babel/preset-react
          @babel/preset-typescript

          從名字上就能看出他們使用的環(huán)境了,需要注意的是env,他的作用是將最新js轉(zhuǎn)換為es6代碼。預(yù)設(shè)是babel插件的組合,我們可以看下package.json(截取一部分):

          image.png
          • @babel/traverse

          ast進(jìn)行遍歷parse

          • ts-node 使用.d.ts文件 既然要開(kāi)發(fā)一個(gè)項(xiàng)目,顯然不會(huì)只有這些代碼。肯定要用到內(nèi)建模塊和第三方模塊。然而,直接導(dǎo)入模塊,在.ts文件中是不行的。例如:

          這是由于typescript自身的機(jī)制,需要一份xx.d.ts聲明文件,來(lái)說(shuō)明模塊對(duì)外公開(kāi)的方法和屬性的類(lèi)型以及內(nèi)容。感覺(jué)有一些麻煩。好在,官方以及社區(qū)已經(jīng)準(zhǔn)備好了方案,來(lái)解決這個(gè)問(wèn)題。

          在TypeScript 2.0以上的版本,獲取類(lèi)型聲明文件只需要使用npm。在項(xiàng)目目錄下執(zhí)行安裝:

          npm install --save-dev @types/node

          就可以獲得有關(guān)node.js v6.x的API的類(lèi)型說(shuō)明文件。之后,就可以順利的導(dǎo)入需要的模塊了:

          import * as http from 'http';

          小試牛刀

          我們安裝好依賴(lài)之后,編寫(xiě)以下代碼

          touch var_to_let.ts
          import { parse } from '@babel/parser'
          import traverse from '@babel/traverse'
          import generate from '@babel/generator'

          //將一段代碼(字符串)轉(zhuǎn)換成 AST

          let code = `let a = 'str'; let b = 2`

          const ast = parse(code, { sourceType: 'module' })

          console.log(ast)

          在vscode里面打上斷點(diǎn)

          接著我們?cè)诮K端運(yùn)行

          就可以進(jìn)行斷點(diǎn)調(diào)試了,我們可以看到parse()函數(shù)把字符串的代碼轉(zhuǎn)換后的結(jié)果

          image.png

          我們?cè)谶@個(gè)ast樹(shù)形結(jié)構(gòu)里面找到以下幾個(gè)屬性,不難發(fā)現(xiàn)ats就是把一個(gè)字符串代碼,表示成一個(gè)樹(shù)形結(jié)構(gòu)。

          image.png

          把let變成 var

          traverse(ast, {
            //遍歷每一個(gè)節(jié)點(diǎn)都會(huì)進(jìn)入的回調(diào)函數(shù)。
            enteritem => {
              if(item.node.type === 'VariableDeclaration') {
                if(item.node.kind === 'let') {
                  item.node.kind = 'var'
                }
              }
            }
          })

          const result = generate(ast, {}, code)
          console.log(result)

          看下面的結(jié)果,我們修改了ats對(duì)象里面的屬性的值,最終通過(guò)generate函數(shù)生成了一個(gè)新的字符串代碼片段。成功的將原始代碼里面的let轉(zhuǎn)化成了var。嗯?Es6轉(zhuǎn)Es5就這么簡(jiǎn)單?我們繼續(xù)

          為什么用AST?

          1. 很難用正則表達(dá)式來(lái)替換,正則表達(dá)式很容易把let a = 'let',替換成var a = 'var'
          2. 在修改的時(shí)候需要知道每一個(gè)單詞的意思,才能做到只修改用于變量聲明let
          3. 而AST能明確的告訴你每個(gè)let的意思

          把代碼轉(zhuǎn)化為ES5

          //使用@babel/core 和 @babel/preset-env把代碼自動(dòng)轉(zhuǎn)化成ES5

          import { parse } from '@babel/parser'
          import * as babel from '@babel/core'

          let code = `let a = 'str'; let b = 2; const c = 100`

          //把字符串轉(zhuǎn)成ast
          const ast = parse(code, { sourceType'module' })

          //把a(bǔ)ts變成字符串
          const result = babel.transformFromAstSync(ast, code, {
            presets: ['@babel/preset-env']
          })

          console.log(result)

          得到以下結(jié)果,可以看到constlet都被轉(zhuǎn)化成了ES5的代碼了。下面我們接著寫(xiě),我們把code字符串代碼放在文件里面,把生成的結(jié)果寫(xiě)入到另一個(gè).es5.js結(jié)尾的文件中。

          //使用@babel/core 和 @babel/preset-env把代碼自動(dòng)轉(zhuǎn)化成ES5

          import { parse } from '@babel/parser'
          import * as babel from '@babel/core'
          import * as fs from 'fs'

          //從文件中讀取源代碼,并轉(zhuǎn)成字符串
          let code = fs.readFileSync('./test.js').toString()

          //把字符串轉(zhuǎn)成ast
          const ast = parse(code, { sourceType'module' })

          //把a(bǔ)ts變成字符串
          const result = babel.transformFromAstSync(ast, code, {
            presets: ['@babel/preset-env']
          })

          //把生成好的字符串寫(xiě)入文件里面
          let fileName = 'test.es5.js'
          fs.writeFileSync(fileName, result.code)

          通過(guò)以上代碼我們就將一個(gè)源文件是ES6js代碼轉(zhuǎn)換成了ES6的代碼

          image.png

          分析index.js的依賴(lài)

          在當(dāng)前目錄下新建project-01目錄,新建三個(gè)文件a.js,b.js,index.js分別寫(xiě)下以下內(nèi)容

          a.js

          var a = {
            value100
          }

          export default a

          b.js

          var b = {
            value : 100
          }

          export default b

          index.js

          import a from './a'
          import b from './b'

          var sum = a.value + b.value 

          console.log(sum)

          因?yàn)楫?dāng)前我們的環(huán)境是node環(huán)境,為了在node里面讓import生效,我們使用以下方法。

          全局安裝 babel-cli

          npm install babel-cli -g

          安裝 babel-preset-env

          npm install babel-preset-env -D

          然后原來(lái)是 node server.js,改為這樣調(diào)用:babel-node --presets env server.js

          ?

          需要注意的是如果只是為了 babel-node 這一個(gè)命令,安裝 babel-cli 會(huì)加載安裝很多資源和模塊,出于性能考慮不推薦在生產(chǎn)環(huán)境使用。自己在開(kāi)發(fā)調(diào)試的時(shí)候,可以鼓搗著玩玩

          ?

          下面我們?cè)诿钚袌?zhí)行以下操作,便可以看到結(jié)果。呀是不是有點(diǎn)跑偏了的感覺(jué),我們是來(lái)分析index.js文件的依賴(lài)項(xiàng)的呀,趕緊回到正題。

          image.png

          在項(xiàng)目下新建deps.ts文件,在文章最后面我會(huì)把完整的代碼放上來(lái),一段一段貼代碼,太浪費(fèi)空間了。

          最終我們得到了想要的結(jié)果。

          image.png

          遞歸分析嵌套的依賴(lài)

          下面我們?cè)偌右稽c(diǎn)難度,假如我們的a.js又依賴(lài)了其他的文件呢?b.js也同樣依賴(lài)了其他文件呢?我們又該如何獲取到其內(nèi)部文件依賴(lài)的依賴(lài)呢?我們繼續(xù)

           cp -r project-01/ project-02
          cd project-02

          mkdir lib

          a.js

          import a1 from "./lib/a1.js"

          var a = {
            value'a'
          }

          export default a

          b.js

          js
          import b1 from "./lib/b1.js"

          var b = {
            value : 100
          }

          export default b

          在之前的a.jsb.js里面分別把這兩個(gè)文件import進(jìn)去, 這樣就有更深層次的依賴(lài)關(guān)系了,我們下面只需要在遍歷AST語(yǔ)法樹(shù)的時(shí)候,當(dāng)發(fā)現(xiàn)這個(gè)節(jié)點(diǎn)是ImportDeclaration的時(shí)候,再獲取這個(gè)節(jié)點(diǎn)的值,組裝一下真實(shí)的文件路徑,再遞歸調(diào)用把組裝好的路徑傳入collectCodeAndDeps便可以繼續(xù)分析了。

          什么是循環(huán)依賴(lài)?

          index.js

          import a from './a.js'
          import b from './b.js'

          console.log(a)
          let sum = a.value + b.value

          console.log(sum)

          a.js

          import b from './b.js'

          var a = {
            value: b.value + 1
          }

          export default a

          b.js

          js
          import a from './a.js'

          var b = {
            value: a.value + 1
          }

          export default b

          我在node版本為v16.3.0下面測(cè)試得到的結(jié)果。

          image.png

          如果我們把上面value的值換成一個(gè)常量的話,就可以正常執(zhí)行完代碼了。

          靜態(tài)分析循環(huán)依賴(lài)

          在遍歷AST的時(shí)候如果發(fā)現(xiàn)在之前的記錄里面已經(jīng)有了,就不再進(jìn)行遍歷了。

          //分析index.js里面代碼依賴(lài)的文件
          import { resolve, relative, dirname } from 'path'
          import { readFileSync } from 'fs'

          import { parse } from '@babel/parser'
          import traverse from '@babel/traverse';


          //設(shè)置項(xiàng)目根目錄


          const projectRoot = resolve(__dirname, 'project-02')

          //聲明最終結(jié)果的類(lèi)型
          var result = {
            'index.js': {
              deps: ['a.js''b.js'],
              code"import a from './a'\r\nimport b from './b'\r\n\r\nvar sum = a.value + b.value \r\n\r\nconsole.log(sum)"
            }
          }

          type DepRelation = {
            [key: string]: {
              deps: string[],
              code: string
            }
          }

          interface a {

          }
          //初始化

          const depRelation: DepRelation = {}

          function collectCodeAndDeps(filepath: string{
            let key = getProjectPath(filepath)
            if (Object.keys(depRelation).includes(key)) {
              // 注意,重復(fù)依賴(lài)不一定是循環(huán)依賴(lài)
              return
            }
            //先讀取index文件的內(nèi)容
            //把字符串代碼轉(zhuǎn)換成ats
            let code = readFileSync(resolve(filepath)).toString()

            //把入口文件的文件名當(dāng)做map的key
            depRelation[key] = {
              deps: [],
              code
            }

            let ast = parse(code, {
              sourceType'module'
            })

            //遍歷ast

            traverse(ast, {
              enterpath => {
                //如果發(fā)現(xiàn)當(dāng)前語(yǔ)句是 import 就把inport的value 寫(xiě)入到依賴(lài)中去
                if (path.node.type === 'ImportDeclaration') {
                  //當(dāng)前文件的上一級(jí)目錄 與獲取到當(dāng)前文件的依賴(lài)文件進(jìn)行拼接。
                  let depAbsolutePath = resolve(dirname(filepath), path.node.source.value)
                  //獲取當(dāng)前文件與根目錄的相對(duì)路徑
                  const depProjectPath = getProjectPath(depAbsolutePath)
                  // 把依賴(lài)寫(xiě)進(jìn) depRelation
                  depRelation[key].deps.push(depProjectPath)
                  //拿到依賴(lài)文件的真實(shí)路徑進(jìn)行再一次依賴(lài)分析
                  collectCodeAndDeps(depAbsolutePath)
                }
              }
            })
          }

          collectCodeAndDeps(resolve(projectRoot, 'index.js'))

          console.log(depRelation)

          //獲取文件相對(duì)跟目錄的相對(duì)路徑
          /*
          C: \\Users\\code\\zf\\webpack\\01\\project - 01
          C: \\Users\\code\\zf\\webpack\\01\\project - 01\\index.js

          //得到的結(jié)果就是index.js
          */

          function getProjectPath(path: string{
            return relative(projectRoot, path).replace(/\\/g'/')
          }

          總結(jié)

          AST相關(guān)

          1. parse:把代碼轉(zhuǎn)換成AST
          2. traverse:遍歷AST,并在需要的時(shí)候可以進(jìn)行修改
          3. generate:把AST再轉(zhuǎn)換成代碼code2

          工具相關(guān)

          1. babel 可以把高級(jí)代碼轉(zhuǎn)換成ES5代碼
          2. @babel/parser
          3. @babel/traverse
          4. @babel/generate
          5. @babel/core
          6. @babel-preset-env 獲取您指定的任何目標(biāo)環(huán)境并根據(jù)其映射檢查它們以編譯插件列表并將其傳遞給 Babel

          代碼技巧

          用哈希表來(lái)存儲(chǔ)映射關(guān)系 通過(guò)檢查哈希表的key來(lái)檢測(cè)重復(fù)

          循環(huán)依賴(lài)

          有的循環(huán)依賴(lài)可以正常執(zhí)行 有的循環(huán)依賴(lài)不可以正常執(zhí)行 但是兩者都可以進(jìn)行靜態(tài)分析


          瀏覽 58
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  开心五月丁香五月 | 北条麻妃视频 | 国产黄在线看 | 一区二区三区四区免费播放 | 91性爱在线播放 |