游戏电视苹果数码历史美丽
投稿投诉
美丽时装
彩妆资讯
历史明星
乐活安卓
数码常识
驾车健康
苹果问答
网络发型
电视车载
室内电影
游戏科学
音乐整形

从零实现一个迷你Webpack

  大厂技术坚持周更精选好文
  本文为来自字节跳动国际化电商S项目的文章,已授权ELab发布。
  webpack是当前使用较多的一个打包工具,将众多代码组织到一起使得在浏览器下可以正常运行,下面以打包为目的,实现一个简易版webpack,支持单入口文件的打包,不涉及插件、分包等。
  前置知识
  举个,先来看看下面这个demo,例子很简单,一个index。js,里面引用了一个文件a。js,a。js内部引入了b。js,通过webpack最简单的配置,将index。js文件作为入口进行打包。
  来看看打包后的内容是怎样的index。js
  require(。a。js);
  console。log(entryload);
  a。js
  require(。b。js);
  consta1;
  console。log(aload);
  module。exportsa;
  b。js
  console。log(bload);
  constb1;
  module。exportsb;
  可以看到打包产物是一个立即执行函数,函数初始先定义了多个module,每个module是实际代码中被require的文件内容,同时由于浏览器不支持require方法,webpack内部自行实现了一个webpackrequire,并将代码中的require全部替换为该函数(从打包结果可看出)。
  在webpackrequire定义之后,便开始执行入口文件,同时可以看出,webpack的打包过程便是通过入口文件,将直接依赖和间接依赖以module的形式组织到一起,并通过自行实现的require实现模块的同步加载。
  了解了打包产物后,便可以开始实现简易版的webpack,最终打包产物与webpack保持一致。
  初始化参数
  根据Node接口webpack中文文档〔1〕可以知道,webpacknodeapi对外暴露出了webpack方法,通过调用webpack方法传入配置,返回compiler对象,compiler对象包含run方法可执行编译,即constwebpackrequire(webpack);引用webpack
  constcompilerwebpack(options);传入配置生成compiler对象
  compiler。run((err,stats){执行编译,传入回调
  });
  因此,首先需要实现一个webpack方法,同时该方法支持传入webpack配置,返回compiler实例,webpack官方支持了以cli的形式运行webpack命令和指定参数、配置文件,这一部分暂时简单实现,我们暴露出一个方法,方法接收用户的配置。miniwebpackcoreindex。js
  functionstrongtoutiaooriginspanclasshighlighttextwebpackstrong{
  创建compiler对象
  constcompilernewCompiler(options);
  }
  module。exportswebpack;
  如上,实现了一个webpack方法,可传入一个options参数,包括用户指定的打包入口entry、output等。webpack({
  entry:。index。js,
  output:{
  path:path。resolve(dirname,dist),
  filename:〔name〕。js,
  },
  module:{
  rules:
  }
  })
  编译
  上面已经实现了webpack配置的传入,compiler的创建,接下来还需要实现Compiler类,该类内部暴露一个run方法,用于执行编译。
  首先需要明确编译过程需要做的事情。
  读取入口文件,将入口文件交给匹配的loader处理,返回处理后的代码
  开始编译loader处理完的代码
  若代码中依赖了其他文件,则对require函数替换为webpack自行实现的webpackrequire,保存该文件的处理结果,同时让其他文件回到第1步进行处理,不断循环。
  编译结束后,每个文件都有其对应的处理结果,将这些文件的编译结果从初始的入口文件开始组织到一起。
  入口文件loader处理
  读取入口文件,将入口文件交给匹配的loader处理miniwebpackcompiler。js
  constfsrequire(fs);
  classCompiler{
  constructor(options){
  this。optionsoptions{};
  保存编译过程编译的module
  this。modulesnewSet;
  }
  run(callback){
  constentryChunkthis。build(path。join(process。cwd,this。options。entry));
  }
  build(modulePath){
  letoriginCodefs。readFileSync(modulePath);
  originCodethis。dealWidthLoader(modulePath,originCode。toString);
  returnthis。dealDependencies(originCode,modulePath);
  }
  将源码交给匹配的loader处理
  dealWidthLoader(modulePath,originCode){
  〔。。。this。options。module。rules〕。reverse。forEach(item{
  if(item。test(modulePath)){
  constloaders〔。。。item。use〕。reverse;
  loaders。forEach(loaderoriginCodeloader(originCode))
  }
  })
  returnoriginCode
  }
  }
  module。exportsCompiler;
  入口文件处理
  这里需要开始处理入口文件的依赖,将其require转换成自定义的webpackrequire,同时将其依赖收集起来,后续需要不断递归处理其直接依赖和间接依赖,这里用到了babel进行处理。调用webpack处理依赖的代码
  dealDependencies(code,modulePath){
  constfullPathpath。relative(process。cwd,modulePath);
  创建模块对象
  constmodule{
  id:fullPath,
  dependencies:该模块所依赖模块绝对路径地址
  };
  处理require语句,同时记录依赖了哪些文件
  constastparser。parse(code,{
  sourceType:module,
  ast:true,
  });
  深度优先遍历语法Tree
  traverse(ast,{
  CallExpression:(nodePath){
  constnodenodePath。node;
  if(node。callee。namerequire){
  获得依赖的路径
  constrequirePathnode。arguments〔0〕。value;
  constmoduleDirNamepath。dirname(modulePath);
  constfullPathpath。relative(path。join(moduleDirName,requirePath),requirePath);
  替换require语句为webpack自定义的require方法
  node。calleet。identifier(webpackrequire);
  将依赖的路径修改成以当前路行为基准
  node。arguments〔t。stringLiteral(fullPath)〕;
  constexitModule〔。。。this。modules〕。find(itemitem。idfullPath)
  该文件可能已经被处理过,这里判断一下
  if(!exitModule){
  记录下当前处理的文件所依赖的文件(后续需逐一处理)
  module。dependencies。push(fullPath);
  }
  }
  },
  });
  根据新的ast生成代码
  const{code:compilerCode}generator(ast);
  保存处理后的代码
  module。sourcecompilerCode;
  返回当前模块对象
  returnmodule;
  }
  依赖处理
  到这里为止便处理完了入口文件,但是在处理文件过程,还收集了入口文件依赖的其他文件未处理,因此,在dealDependencies尾部,加入以下代码调用webpack处理依赖的代码
  dealDependencies(code,modulePath){
  。。。
  。。。
  。。。
  为当前模块挂载新的生成的代码
  module。sourcecompilerCode;
  递归处理其依赖
  module。dependencies。forEach((dependency){
  constdepModulethis。build(dependency);
  同时保存下编译过的依赖
  this。modules。add(depModule);
  });
  。。。
  。。。
  。。。
  返回当前模块对象
  returnmodule;
  }
  Chunk
  在上面的步骤中,已经处理了入口文件、依赖文件,但目前它们还是分散开来,在webpack中,是支持多个入口,每个入口是一个chunk,这个chunk将包含入口文件及其依赖的moduleminiwebpackcompiler。js
  constfsrequire(fs);
  classCompiler{
  constructor(options){
  this。optionsoptions{};
  保存编译过程编译的module
  this。modulesnewSet;
  }
  run(callback){
  constentryModulethis。build(path。join(process。cwd,this。options。entry));
  constentryChunkthis。buildChunk(entry,entryModule);
  }
  build(modulePath){
  }
  将源码交给匹配的loader处理
  dealWidthLoader(modulePath,originCode){
  }
  调用webpack处理依赖的代码
  dealDependencies(code,modulePath){
  }
  buildChunk(entryName,entryModule){
  return{
  name:entryName,
  入口文件编译结果
  entryModule:entryModule,
  所有直接依赖和间接依赖编译结果
  modules:this。modules,
  };
  }
  }
  module。exportsCompiler;
  文件生成
  至此我们已经将入口文件和其所依赖的所有文件编译完成,现在需要将编译后的代码生成对应的文件。
  根据最上面利用官方webpack打包出来的产物,保留其基本结构,将构造的chunk内部的entryModule的source以及modules的souce替换进去,并根据初始配置的output生成对应文件。miniwebpackcompiler。js
  constfsrequire(fs);
  classCompiler{
  constructor(options){
  this。optionsoptions{};
  保存编译过程编译的module,下面会讲解到
  this。modulesnewSet;
  }
  run(callback){
  constentryModulethis。build(path。join(process。cwd,this。options。entry));
  constentryChunkthis。buildChunk(entry,entryModule);
  this。generateFile(entryChunk);
  }
  build(modulePath){
  }
  将源码交给匹配的loader处理
  dealWidthLoader(modulePath,originCode){
  }
  调用webpack处理依赖的代码
  dealDependencies(code,modulePath){
  }
  buildChunk(entryName,entryModule){
  }
  generateFile(entryChunk){
  获取打包后的代码
  constcodethis。getCode(entryChunk);
  if(!fs。existsSync(this。options。output。path)){
  fs。mkdirSync(this。options。output。path);
  }
  写入文件
  fs。writeFileSync(
  path。join(
  this。options。output。path,
  this。options。output。filename。replace(〔name〕,entryChunk。name)
  ),
  code
  );
  }
  getCode(entryChunk){
  return
  ({
  webpackBootstrap
  varwebpackmodules{
  {entryChunk。modules。map(module
  {module。id}:(module,unusedwebpackexports,webpackrequire){
  {module。source}
  }
  )。join(,)}
  };
  varwebpackmodulecache{};
  functionwebpackrequire(moduleId){
  Checkifmoduleisincache
  varcachedModulewebpackmodulecache〔moduleId〕;
  if(cachedModule!undefined){
  returncachedModule。exports;
  }
  Createanewmodule(andputitintothecache)
  varmodule(webpackmodulecache〔moduleId〕{
  exports:{},
  });
  Executethemodulefunction
  webpackmodules〔moduleId〕(
  module,
  module。exports,
  webpackrequire
  );
  Returntheexportsofthemodule
  returnmodule。exports;
  }
  varwebpackexports{};
  ThisentryneedtobewrappedinanIIFEbecauseitneedtobeisolatedagainstothermodulesinthechunk。
  ({
  {entryChunk。entryModule。source};
  });
  })
  ;
  }
  }
  module。exportsCompiler;
  试试在浏览器下跑一下生成的代码
  符合预期,至此便完成了一个极简的webpack,针对单入口文件进行打包。当然真正的webpack远非如此简单,这里仅仅只是实现其一个打包思路。
  谢谢支持
  以上便是本次分享的全部内容,希望对你有所帮助
  欢迎关注公众号ELab团队收货大厂一手好文章字节跳动校社招内推码:WWCM1TA
  投递链接:https:job。toutiao。comsrj1fwQW
  可凭内推码投递字节跳动国际化电商S项目团队相关岗位哦
  参考资料
  〔1〕
  Node接口webpack中文文档:https:webpack。docschina。orgapinodewebpack
  END

金辉恋曲四重奏GoldenTime等一个人,还是等一个故事?Hello!大家早上好这一期给大家带来《金辉恋曲四重奏GoldenTime》的鉴赏。本来想拖到七夕再发的,因为游戏剧情内容十分应景。在推荐的过程中干脆给大家带来恋爱忠告当然也是……基本属实!曝马布里辞职被驳回,北控管理层得寸进尺,如今已悔悟北控队利用两个赛季就已经成为了联赛当中的流量球队,无论是曝光度还是关注度和以前相比都天差地别,作为一支成立不久的球队,北控队在搬到北京以来就一直被压在首钢身下,特别是在成绩不佳……世界足坛最昂贵的五座奖杯!梅西仅凭着七座金球奖就发财了赢得比赛和奖杯是任何一支足球队的首要目标,足球已经成为最受欢迎的运动,世界各地的球迷都能认出一些奖杯的样子。对于一支球队来说,没有什么比在比赛结束时把奖杯带回家更好的感觉……钠离子电池理想丰满现实骨感,未来可能没有钠么美本文不作为投资依据。近段时间,钠电池受到资本市场关注,尤其11月29日中科海钠(阜阳)全球首条GWh级钠离子电池生产线产品下线,标志着该生产线正式具备了规模化生产吉瓦时级……全球第一也遇难题!传三星库存高达5000万台,低端手机卖不出行业不景气不只是国内市场,就连海外手机市场也是一样,作为全球第一的三星也遇到了大难题。库存的问题,现在似乎越来越严重了。2022年的手机市场,谁也没有想到会比2021年更……最便宜的新能源,五菱宏光MINIEV是否只是个会移动的铁壳子五菱宏光MINIEV2022款悦享款磷酸铁锂是一款颜色搭配和体型都比较可爱的车,从外观来看,这款车很适合女孩子来开,4。48万的价格也不会让人感到很大的压力,那么这款车究竟怎么……一场20给至暗的国足带来星星之火,他们像不像14年前高家军?国足主帅和足协主席均因为严重违法违纪被带走调查,俱乐部一个接一个退出,球员涉嫌打假球、被小三举报已经跌落谷底的中国足球,似乎眼前是一片黑,根本就看不到光。但是3月6日晚的……逆袭!归化收欧洲联赛橄榄枝作出回应,谭凯元或成第二位留洋球员中国足球经历过一系列糟糕事件之后,终于迎来了一丝丝安慰,那就是年轻球员开启留洋之旅。根据国内媒体的消息称,归化球员戴伟浚收到了法国和土耳其的欧洲联赛的橄榄枝,他的心愿就是前往一……杜锋不满CBA外援政策,吐槽变化有点多,季后赛提防黑马双外援北京时间4月1日,CBA季后赛将展开争夺,12进8的比赛,广东对阵天津,这也是联赛第5和第12的对决。从最新的消息看,季后赛天津队依然有外援使用的优势,对此杜锋也表达了自己的意……大同土林游记作者张德富大同土林游记作者张德富1,古老的故事讲不完石板河是一条桑干河支流的古河存在上几十万年悠悠岁月微一沉吟弯腰注定与生不凡……小米新专利屏幕可向后翻折,然后摄像头是前后通用一组的思路小米手机新专利曝光,这次的手机设计是屏幕可以翻开,然后前置摄像头变成后置摄像头的思路。其实这个也是折叠屏的设计一种,但这个设计想要量产估计需要时间,毕竟这个手机不轻薄。从……努比亚Z40SPro外观公布努比亚将于7月20日发布新机努比亚Z40SPro。努比亚官方公布了这款手机的外观,努比亚Z40SPro正面采用居中打孔直屏,边框看起来很窄,后置三颗摄像头,加上环形闪光灯……
荣耀X10全部配色曝光,配备IMX600y与麒麟820IT之家5月16日消息昨日,数码博主数码闲聊站曝光了荣耀X10的更多细节参数。爆料信息显示,荣耀X10支持90Hz刷新率,采用玻璃机身,搭载IMX600y,拥有4300m……拓宽技能人才成长路,厚植人力资源优势近日印发的《关于加强新时代高技能人才队伍建设的意见》明确,健全技能人才培养、使用、评价、激励制度,激励更多劳动者特别是青年一代走技能成才、技能报国之路。千秋伟业,人才为本……人类究竟多渺小?已飞238亿公里的飞船,最后拍摄的照片让人敬数百万年前,人类出现在地球上。漫长的时间过去了,人类创造了灿烂的文明,对地球表面进行了一系列改造。随着宇航科技的出现,人类终于有机会离开地球,去太空看看我们生活的星球是怎样的,……腾讯QQ音乐App内测新增适配折叠屏感谢IT之家网友百口莫辩的线索投递!IT之家3月22日消息在今年2月份,三星发布了GalaxyFold折叠屏手机,华为也发布了MateX折叠屏5G手机,而最新发布的And……腾讯QQ音乐已从GooglePlay商店下架,海外用户听歌受感谢IT之家网友zyaaaaa的线索投递!IT之家1月17日消息据IT之家网友在IT圈反馈,近期腾讯QQ音乐已经从谷歌GooglePlay应用商店下架,这对于海外用户来说……华为手机为什么需要谷歌服务框架许可?IT之家9月1日消息近日谷歌官方表示未来华为智能手机等硬件将无法使用谷歌服务框架(GMS),此举也在网上引起了讨论。那么什么是谷歌服务框架(GMS)呢?华为又为什么需要它的相关……报告诺基亚手机是更新最快的,96的手机运行安卓9PieIT之家8月31日消息据外媒报道,CounterpointResearch最近收集了各大品牌的安卓升级计划数据,追踪了有多少款手机升级到Android9Pie(或推出),重点关……仅1459的5G手机,12GB256GB,还有80W闪充如今,手机已经成为了人们生活中不可或缺的一部分。无论是用于工作、娱乐还是社交,手机都已经成为了人们日常生活中必不可少的工具。而随着科技的不断发展,手机的性能和功能也在不断……协同双赢!农行山东分行与胜利油田开始银企战略合作3月22日,中国农业银行山东省分行与中国石化集团胜利石油管理局有限公司、中国石油化工股份有限公司胜利油田分公司银企战略合作签约仪式在东营胜利宾馆举行。农行山东省分行党委书记、行……RedmiK40游戏增强版明日最高闪降200元,12GB25IT之家6月29日消息今日下午,Redmi红米手机官方表示,RedmiK40游戏增强版明日最高闪降200元,12GB128GB版本2299元,12GB256GB版本2499元。……RedmiK40顶配版回归首发价12GB256GB,2499IT之家5月24日消息今日RedmiK40顶配版回归首发价,12GB256GB版本闪降200元,到手价2499元,价格跟8GB256GB版保持一致。这款手机标准版搭载高通骁龙8……99元定金,华为Mate20全系列智能手机开启预定通道IT之家10月17日消息华为今日开启了全系列新品预售,定金99元,10月26日补齐尾款。华为最新旗舰,超强配置,7nm麒麟980智能芯片,大广角徕卡三摄,安卓年度机皇候选:京东……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网