手写rollup

本文详细介绍了如何使用手写Rollup构建项目,从初始化、添加依赖到实现tree-sharking优化。步骤包括配置entry point、编写核心Rollup函数、解析和打包模块,以及使用scope和ast分析来提升代码效率。
摘要由CSDN通过智能技术生成

手写rollup

初始化项目安装依赖

yarn init -y
yarn add acorn magic-string

添加src/main.js

var a = 1
var b = 2

添加debugger.js

const path = require('path')

const rollup = require('./lib/rollup')

let entry = path.resolve(__dirname,'src/main.js')
rollup(entry,'bundle.js')

添加lib/rollup.js

const Bundle = require('./bundle')
function rollup(entry,outputFileName){
    // c:\Users\Administrator\Desktop\demo\bind-rollup\src\mian.js bundle.js
    const bundle = new Bundle({entry})
    bundle.build(outputFileName)
}
module.exports = rollup

添加lib/bundle.js

const fs = require('fs')
const { default: MagicString } = require('magic-string')
const Module  = require('./moudle')
class Bundle{
    constructor(options){
        this.entryPath = options.entry.replace(/\.js$/,'')+ '.js'
    }
    build(outputFileName){
        // 拿到入口文件
        let entryModule = this.fetchModule(this.entryPath)
        this.statements = entryModule.expandAllStatements()
        const { code } =this.generate()
        fs.writeFileSync(outputFileName,code,'utf-8')
    }
    fetchModule(improtee){
        let route = improtee
        if(route){
            // 拿到入口文件的内容
            let code = fs.readFileSync(route,'utf-8')

            let module = new Module({
                code,
                path:route,
                bundle:this
            })
            return module
        }
    }
    generate(){
        let magicString = new MagicString.Bundle()

        this.statements.forEach(statement=>{
            const source = statement._source.clone()
            magicString.addSource({
                content:source,
                separator:'\n'
            })
        })

        return {code:magicString.toString()}

    }
}

module.exports = Bundle

添加lib/moudle.js

const { parse } = require("acorn")
const { default: MagicString } = require("magic-string")
const analyse = require("./ast/analyse")

class Module {
    constructor({code,path,bundle}){
        this.code = new MagicString(code,{filename:path})
        this.path = path
        this.bundle = bundle
        // 获取ast语法树

        this.ast = parse(code,{
            ecmaVersion:7,
            sourceType:'module'
        })

        this.analyse()

    }
    analyse(){
        analyse(this.ast,this.code,this)
    }
    expandAllStatements(){
        let allStatements = []
        this.ast.body.forEach(statement => {
            let statements = this.expandStatements(statement)
            allStatements.push(...statements)
        });
        return allStatements
    }
    expandStatements(statement){
        let result = []
        if(!statement._included){
            statement._included = true
            result.push(statement)
        }
        return result
    }
}

module.exports = Module

添加lib/ast/analyse.js

function analyse(ast,magicString,module){
    ast.body.forEach(statement => {
        Object.defineProperties(statement,{
            _source:{
                value:magicString.snip(statement.start,statement.end)
            }
        })
    });
}

module.exports = analyse

出现下图说明打包成功

在这里插入图片描述

rollup的tree-sharking

修改main.js

import {name,age} from './msg'

function say(){
    console.log('hello',name)
}

say()
  • msg.js
export const name = 'xiaoshunshi'
export const age = 19

添加作用域函数scope.js

class Scope{
    constructor(options = {}){
        this.name = options.name
        this.parent = options.parent
        this.names = options.params || []
    }
    add(name){
        this.names.push(name)
    }
    findDefiningScope(name){
        if(this.names.includes(name)){
            return this
        }
        if(this.parent){
            return this.parent.findDefiningScope(name)
        }
        return null
    }
}

module.exports = Scope

添加遍历节点方法walk.js

function walk(ast,{enter,leave}){
    visit(ast,null,enter,leave)
}

function visit(node,parent,enter,leave){
    if(enter){
        enter.call(null,node,parent)
    }

    let childKeys = Object.keys(node).filter(key => typeof node[key] === 'object')
    childKeys.forEach(childKey=>{
        let value = node[childKey]
        if(Array.isArray(value)){
            value.forEach(val=>visit(val,node,enter,leave))
        }else if(value && value.type){
            visit(value,node,enter,leave)
        }
    })
    if(leave){
    
        leave.call(null,node,parent)
    }
}

module.exports = walk

修改lib/ast/analyse.js

const Scope = require("./scope");
const walk = require("./walk");

function analyse(ast, magicString, module) {
    let scope = new Scope()

    ast.body.forEach(statement => {
        function addToScope(declaration) {
            var name = declaration.id.name
            scope.add(name)
            if (!scope.parent) {
                statement._defines[name] = true
            }
        }
        Object.defineProperties(statement, {
            _defines: {
                value: {}
            },
            _dependsOn: {
                value: {}
            },
            _included: {
                value: false,
                writable: true
            },
            _source: {
                value: magicString.snip(statement.start, statement.end)
            }
        })

        walk(statement, {
            enter(node) {
                let newScope

                switch (node.type) {
                    case 'FunctionDeclaration':
                        const params = node.params.map(x => x.name)
                        
                        if(node.type === 'FunctionDeclaration'){
                            addToScope(node)
                        }
                        newScope = new Scope({
                            parent: scope,
                            params
                        })
                        break;
                    case 'VariableDeclaration':
                        node.declarations.forEach(addToScope)
                        break;
                    default:
                        break;
                }

                if(newScope){
                    Object.defineProperty(node,'_scope',{
                        value:newScope
                    })
                    scope = newScope
                }

            },
            leave(node) {
                if(node._scope){
                    scope = scope.parent
                }
            }
        })
    })

    ast.body.forEach(statement=>{
        walk(statement,{
            enter(node){
                if(node._scope){
                    scope = node._scope
                }
                if(node.type === 'Identifier'){
                    const definingScope = scope.findDefiningScope(node.name)
                    if(!definingScope){
                        statement._dependsOn[node.name] = true
                    }
                }
            },
            leave(node){
                if(node._scope){
                    scope = scope.parent
                }
            }

        })
    })

}

module.exports = analyse

修改lib/bundle.js

const fs = require('fs')
const { default: MagicString } = require('magic-string')
const Module  = require('./moudle')
const path = require('path')
class Bundle{
    constructor(options){
        this.entryPath = options.entry.replace(/\.js$/,'')+ '.js'


    }
    build(outputFileName){
        // 拿到入口文件
        let entryModule = this.fetchModule(this.entryPath)
        this.statements = entryModule.expandAllStatements()

        const { code } =this.generate()
        fs.writeFileSync(outputFileName,code,'utf-8')

    }
    fetchModule(improtee,importer){
        let route = improtee
        if(!importer){
            route = improtee
    
        }else{
            if(path.isAbsolute(improtee)){
                route = improtee
            }else if(improtee[0] == '.'){
                route = path.resolve(path.dirname(importer),improtee.replace(/\.js$/,'') + '.js')
                
            }
        }
        if(route){
            // 拿到入口文件的内容
            let code = fs.readFileSync(route,'utf-8')

            let module = new Module({
                code,
                path:route,
                bundle:this
            })
            return module
        }
    }
    generate(){
        let magicString = new MagicString.Bundle()

        this.statements.forEach(statement=>{
            const source = statement._source.clone()
            magicString.addSource({
                content:source,
                separator:'\n'
            })
        })

        return {code:magicString.toString()}

    }
}

module.exports = Bundle

修改lib/moudle.js

const { parse } = require("acorn")
const { default: MagicString } = require("magic-string")
const analyse = require("./ast/analyse")

function hasOwnProperty(obj, prop) {
    return Object.prototype.hasOwnProperty.call(obj, prop)
}
class Module {
    constructor({code,path,bundle}){
        this.code = new MagicString(code,{filename:path})
        this.path = path
        this.bundle = bundle
        // 获取ast语法树

        this.ast = parse(code,{
            ecmaVersion:7,
            sourceType:'module'
        })

        this.analyse()

    }
    analyse(){
        this.imports = {}
        this.exports = {}
        this.ast.body.forEach(node =>{
            if(node.type === 'ImportDeclaration'){
                let source = node.source.value
                let specifiers = node.specifiers
    
                specifiers.forEach(specifier =>{
                    const name = specifier.imported.name
                    const loaclName = specifier.local.name
                    // console.log(name,loaclName)

                    this.imports[loaclName] = {
                        name,
                        loaclName,
                        source
                    }
                })
            }else if(node.type === 'ExportNamedDeclaration'){
                let declaration = node.declaration
                if(declaration.type === 'VariableDeclaration'){
                    let name = declaration.declarations[0].id.name

                    this.exports[name] = {
                        node,localName:name,expression:declaration
                    }
                }
            }
           
        })
        analyse(this.ast,this.code,this)
        this.definitions = {}
        this.ast.body.forEach(statement=>{
            Object.keys(statement._defines).forEach(name=>{
                this.definitions[name] = statement
            })
        })
    }
    expandAllStatements(){
        let allStatements = []

        this.ast.body.forEach(statement => {


            if(statement.type === 'ImportDeclaration'){
                return 
            }
            let statements = this.expandStatements(statement)
            allStatements.push(...statements)
        });
        return allStatements
    }
    expandStatements(statement){
        let result = []
        const dependecies = Object.keys(statement._dependsOn)
        dependecies.forEach(name=>{
            let definition = this.define(name)
            result.push(...definition)
        })
        if(!statement._included){
            statement._included = true
            result.push(statement)
        }
        return result

        
    }
    define(name){
        if(hasOwnProperty(this.imports,name)){
            const improtData = this.imports[name]
            const module = this.bundle.fetchModule(improtData.source,this.path)
            const exportData = module.exports[improtData.name]
            return module.define(exportData.localName)
        }else{
            let statement = this.definitions[name]
            if(statement && !statement._included){
                return this.expandStatements(statement)
            }else{
                return []
            }
        }
    }
}

module.exports = Module
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值