最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • webpack系列学习-详细的实现简易webpack

    正文概述 掘金(天霸动霸)   2021-01-28   514

    前言:笔者把学习的webpack知识从基础到原理写个系列,以便回顾。希望能帮助到更多正在学习webpack的小伙伴。

    webpack系列学习-初体验

    webpack系列学习-基本用法一

    webpack系列学习-各种loader使用

    webpack系列学习-热更新和压缩

    webpack系列学习-使用eslint和发布npm包

    webpack系列学习-构建webpack配置

    前言:实现一个如下功能的简易webpack

    1.将ES6语法转换成ES5的语法

    • 通过 Babylon 生成AST
    • 通过 babel-core 将AST 重新生成源码

    2.分析模块之间的依赖关系

    • 通过 babel-traverse 的 ImportDeclaration方法获取依赖属性

    3.生成的js文件可以在浏览器运行

    现在开始:

    初始化项目

    mkdir simple_webpack
    cd simple_webpack
    npm init -y
    

    新建项目目录

    webpack系列学习-详细的实现简易webpack

    • 目录解释:

      • lib:simple_webpack的源码
      • src:业务代码的入口
      • simplepack.config.js:相当于webpack.config.js
    • 首先在simplepack.config.js中配置输入和输出

    
    const path = require('path');
    
    module.exports = {
      entry: path.join(__dirname, './src/index.js'),
      output: {
        path: path.join(__dirname, './dist'),
        filename: 'main.js',
      },
    };
    
    • 在src目录下创建index.js和greeting.js
    
    // index.js
    import { greeting } from './greeting';
    
    document.write(greeting('curry'));
    
    // greeting.js
    // 使用ES6语法
    export function greeting(name) {
      return `hello ${name}`;
    }
    

    在lib目录下创建文件

    webpack系列学习-详细的实现简易webpack

    • 目录解释:
      • index.js:入口文件
      • parser.js:解析AST语法树,转换成源码,将ES6转换成ES5,分析依赖
      • compiler.js:执行最后文件的输出

    开始源码编写

    • 首先在compiler.js输出一个Compiler类,包括下面的属性和方法
    // compiler.js
    module.exports = class Compiler {
      constructor(options) {
        // 这里的options就是simplepack导出的配置项
        const { entry, output } = options;
        this.entry = entry;
        this.output = output;
      }
      
      run() {}
    
      // 模块构建
      buildModule() {}
    
      // 输出文件
      emitFiles() {}
    };
    
    • 在index.js中实例化Compiler类
    const Compiler = require('./compiler');
    const options = require('../simplepack.config');
    
    new Compiler(options);
    
    • 然后编写parser.js,这里做的是转换成AST树,将ES6转换成ES5,分析依赖
    // parser.js
    
    module.exports = {
      // 生成AST树,根据文件路径生成
      getAST: path => {
        
      }
    }
    
    • 生成AST树,需要借助babylon,先安装下。
    npm i babylon -S
    
    • 继续编写getAST方法
    
    const babylon = require('babylon'); // 引入babylon
    const fs = require('fs');  // 引入node中fs模块
    
    module.exports = {
      // 生成AST树
      getAST: path => {
        // 同步读取文件
        const source = fs.readFileSync(path, 'utf-8');
        // 使用babylon的parse方法进行生成AST
        return babylon.parse(source, {
          sourceType: 'module',
        });
      },
    };
    
    
    • 现在getAST方法写好了,我们来测试下。在lib目录下创建test.js
    // lib/test.js
    
    const { getAST } = require('./parser');
    const path = require('path');
    
    console.log(getAST(path.join(__dirname, '../src/index.js')));
    
    • 执行node lib/test.js看下转换效果

    webpack系列学习-详细的实现简易webpack

    • 下面接着写 分析依赖的方法:
    
    // lib/parser.js
    
    module.exports = {
      // ...
      getDependencies: () => {},
    }
    
    • 进行依赖分析,需要借助babel-traverse,这里安装下。
    npm i babel-traverse -S
    
    • 接着回来写分析依赖的方法
    const traverse = require('babel-traverse').default;
    
    module.exports = {
      // ...
      getDependencies: ast => {
        const dependencies = [];
        traverse(ast, {
          // ImportDeclaration:分析import语句
          ImportDeclaration: ({ node }) => {
            // 将依赖push到dependencies中
            dependencies.push(node.source.value);
          },
        });
        // 将依赖返回
        return dependencies;
      },
    };
    
    • 接下来,测试下这个方法
    // lib/test.js
    const { getAST, getDependencies } = require('./parser');
    const path = require('path');
    
    const ast = getAST(path.join(__dirname, '../src/index.js'));
    console.log(getDependencies(ast));
    
    • 执行 node lib/test.js,可以看到出现了依赖文件

    webpack系列学习-详细的实现简易webpack

    • 现在把ES6转成了AST树,接下来将AST树转换成源码,也就是ES5
    
    // lib/parser.js
    
    module.exports = {
      // 将AST树转换成ES5
      transform: (ast) => {},
    }
    
    • 将AST树转换成ES5,需要借助babel-core,先安装下
    npm i babel-core -S
    
    • 回来写transform方法
    const { transformFromAst } = require('babel-core');
    module.exports = {
      transform: ast => {
        const { code } = transformFromAst(ast, null, {
          presets: ['env'],
        });
        return code;
      },
    }
    
    • 此时安装下env
    npm i @babel/preset-env babel-preset-env -S
    
    • 在根目录下创建.babelrc
    
    {
        "presets": ["@babel/preset-env"]
    }
    
    • 测试下transform方法
    
    // lib/test.js
    const { getAST, getDependencies, transform } = require('./parser');
    const path = require('path');
    
    const ast = getAST(path.join(__dirname, '../src/index.js'));
    const dep = getDependencies(ast);
    const source = transform(ast);
    console.log(source);
    
    • 执行 node lib/test.js , 可以看到打印出了源码

    webpack系列学习-详细的实现简易webpack

    • 到此就写完了parser.js中的方法。

    接下来开始写compiler.js中的方法

    • 首先需要在index.js中执行run方法
    
    // lib/index.js
    const Compiler = require('./compiler');
    const options = require('../simplepack.config');
    
    new Compiler(options).run();
    
    • 开始写compiler.js中的buildModule
    module.exports = class Compiler {
      constructor(options) {
        const { entry, output } = options;
        this.entry = entry;
        this.output = output;
      }
      run() {
        const entryModule = this.buildModule(this.entry, true);
      }
    
      // 模块构建
      buildModule(filename, isEntry) {
        let ast;
        if (isEntry) {
          ast = getAST(filename);
        } else {
          // 这里需要找到绝对路径,通过path转换下
          const absolutePath = path.join(process.cwd(), './src', filename);
          ast = getAST(absolutePath);
        }
        return {
          filename,
          dependencies: getDependencies(ast),
          source: transform(ast),
        };
      }
    
      // 输出文件
      emitFiles() {}
    };
    
    • 接着写run方法,此时我们先可以打印下entryModule,查看结果,是在buildModule中返回的。

    webpack系列学习-详细的实现简易webpack

    • 我们需要把依赖全部放到一个数组中,定义this.modules来填充依赖。
    // lib/compiler.js
    const { getAST, getDependencies, transform } = require('./parser');
    const path = require('path');
    module.exports = class Compiler {
      constructor(options) {
        const { entry, output } = options;
        this.entry = entry;
        this.output = output;
        this.modules = [];
      }
      run() {
        const entryModule = this.buildModule(this.entry, true);
        // 把依赖全部push到modules中
        this.modules.push(entryModule);
        // 遍历递归
        this.modules.map(_module => {
          _module.dependencies.map(dependency => {
            this.modules.push(this.buildModule(dependency));
          });
        });
        console.log(this.modules)
      }
    
      // 模块构建
      buildModule(filename, isEntry) {
        let ast;
        if (isEntry) {
          ast = getAST(filename);
        } else {
          const absolutePath = path.join(process.cwd(), './src', filename);
          ast = getAST(absolutePath);
        }
        return {
          filename,
          dependencies: getDependencies(ast),
          source: transform(ast),
        };
      }
    
      // 输出文件
      emitFiles() {}
    };
    
    • 打印下modules

    webpack系列学习-详细的实现简易webpack

    • 接下来,拿到所有依赖后,就要输出文件,在run方法中执行this.emitFiles方法
    
    modules.exports = {
      run(){
        // ...
        this.emitFiles()
      },
      emitFiles() {
        const outputPath = path.join(this.output.path, this.output.filename);
        // 
        let modules = '';
        this.modules.map(_module => {
          modules += `'${_module.filename}': function(require,module,exports){${_module.source}},`;
        });
        // 自执行
        const bundle = `(function(modules){
            function require(filename){
                var fn = modules[filename];
                var module = { exports: {}};
                fn(require, module, module.exports)
                return module.exports;
            }
            require('${this.entry}')
        })({${modules}})`;
    
        console.log('bundle', bundle);
        fs.writeFileSync(outputPath, bundle, 'utf-8');
      }
    }
    
    • 打印下最后的bundle,如下:

    webpack系列学习-详细的实现简易webpack

    • 手动创建下dist目录,执行node lib/index.js。可以看到dist目录下就有了打包好的文件

    webpack系列学习-详细的实现简易webpack

    • 在dist创建index.html,并引入main.js,在浏览器中打开index.html查看效果

    webpack系列学习-详细的实现简易webpack

    • 至此完成了一个简易的webpack

    以上代码放到了github

    可以下载调试。


    起源地下载网 » webpack系列学习-详细的实现简易webpack

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元