webpack把react编译成weapp

更新日期: 2022-09-17阅读: 846标签: webpack

引言

webpack@5.69.0
之前使用taro@3.5.5创建一个taro-react项目之后build了一个weapp项目


最后一步就是启用webpack把react代码编译成weapp

webpack入口

在package.json中可以找到入口文件
这个文件的作用就是引入webpack核心函数(lib/webpack.js)以及工具文件,抛出整理好之后的webpack核心函数

"main": "lib/index.js",

最终输出:

// lib/index.js
// mergeExports是处理fn的,最后输出的就是结果处理的fn
module.exports = mergeExports(fn, {
 // 很多webpack内置插件
 get webpack() {
     return require("./webpack"); // 核心文件
 },
 ......
})

mergeExports

// lib/index.js
// 第一个参数是对象(函数继承于Fuction,Fuction继承于Object)
const mergeExports = (obj, exports) => {
 // 克隆exports对象,传入这个对象里有很多的文件的引入
 const descriptors = Object.getOwnPropertyDescriptors(exports);
 // 遍历这个对象
 for (const name of Object.keys(descriptors)) {
     const descriptor = descriptors[name];
     if (descriptor.get) {
         const fn = descriptor.get;
         // 遍历出的属性一个个添加getter到传入的webpack函数
         Object.defineProperty(obj, name, {
             configurable: false,
             enumerable: true,
             /**
               * memoize就是执行了传入的fn返回一个返回执行之后的结果的一个函数
               * memoize(fn)等于
               * function(){
               *   return fn();
               * } 
               */
             get: memoize(fn)
         });
     } else if (typeof descriptor.value === "object") {
         Object.defineProperty(obj, name, {
             configurable: false,
             enumerable: true,
             writable: false,
             value: mergeExports({}, descriptor.value) // 递归
         });
     } else {
         throw new Error(
             "Exposed values must be either a getter or an nested object"
         );
     }
 }
 // 返回了一个冻结之后的对象,这个对象是个函数,函数里面有很多添加进去的属性方法
 return /** @type {A & B} */ (Object.freeze(obj)); 
};

打印descriptor可以得到如下

{
  get: [Function: get webpack],
  set: undefined,
  enumerable: true,
  configurable: true
}
...
{
  value: {
 ModuleDependency: [Getter],
 ConstDependency: [Getter],
 NullDependency: [Getter]
  },
  writable: true,
  enumerable: true,
  configurable: true
}
...

打印最终返回的

[Function: f] {
  webpack: [Getter],
  validate: [Getter],
  validateSchema: [Getter],
  version: [Getter],
  cli: [Getter],
  AutomaticPrefetchPlugin: [Getter],
  AsyncDependenciesBlock: [Getter],
  BannerPlugin: [Getter],
  Cache: [Getter],
  Chunk: [Getter],
  ChunkGraph: [Getter],
  CleanPlugin: [Getter],
  Compilation: [Getter],
  Compiler: [Getter],
  ConcatenationScope: [Getter],
  ContextExclusionPlugin: [Getter],
  ContextReplacementPlugin: [Getter],
  DefinePlugin: [Getter],
  DelegatedPlugin: [Getter],
  Dependency: [Getter],
  DllPlugin: [Getter],
  DllReferencePlugin: [Getter],
  DynamicEntryPlugin: [Getter],
  EntryOptionPlugin: [Getter],
  EntryPlugin: [Getter],
  EnvironmentPlugin: [Getter],
  EvalDevToolModulePlugin: [Getter],
  EvalSourceMapDevToolPlugin: [Getter],
  ExternalModule: [Getter],
  ExternalsPlugin: [Getter],
  Generator: [Getter],
  HotUpdateChunk: [Getter],
  HotModuleReplacementPlugin: [Getter],
  IgnorePlugin: [Getter],
  JavascriptModulesPlugin: [Getter],
  LibManifestPlugin: [Getter],
  LibraryTemplatePlugin: [Getter],
  LoaderOptionsPlugin: [Getter],
  LoaderTargetPlugin: [Getter],
  Module: [Getter],
  ModuleFilenameHelpers: [Getter],
  ModuleGraph: [Getter],
  ModuleGraphConnection: [Getter],
  NoEmitOnErrorsPlugin: [Getter],
  NormalModule: [Getter],
  NormalModuleReplacementPlugin: [Getter],
  MultiCompiler: [Getter],
  Parser: [Getter],
  PrefetchPlugin: [Getter],
  ProgressPlugin: [Getter],
  ProvidePlugin: [Getter],
  RuntimeGlobals: [Getter],
  RuntimeModule: [Getter],
  SingleEntryPlugin: [Getter],
  SourceMapDevToolPlugin: [Getter],
  Stats: [Getter],
  Template: [Getter],
  UsageState: [Getter],
  WatchIgnorePlugin: [Getter],
  WebpackError: [Getter],
  WebpackOptionsApply: [Getter],
  WebpackOptionsDefaulter: [Getter],
  WebpackOptionsValidationError: [Getter],
  ValidationError: [Getter],
  cache: { MemoryCachePlugin: [Getter] },
  config: {
 getNormalizedWebpackOptions: [Getter],
 applyWebpackOptionsDefaults: [Getter]
  },
  dependencies: {
 ModuleDependency: [Getter],
 ConstDependency: [Getter],
 NullDependency: [Getter]
  },
  ids: {
 ChunkModuleIdRangePlugin: [Getter],
 NaturalModuleIdsPlugin: [Getter],
 OccurrenceModuleIdsPlugin: [Getter],
 NamedModuleIdsPlugin: [Getter],
 DeterministicChunkIdsPlugin: [Getter],
 DeterministicModuleIdsPlugin: [Getter],
 NamedChunkIdsPlugin: [Getter],
 OccurrenceChunkIdsPlugin: [Getter],
 HashedModuleIdsPlugin: [Getter]
  },
  javascript: {
 EnableChunkLoadingPlugin: [Getter],
 JavascriptModulesPlugin: [Getter],
 JavascriptParser: [Getter]
  },
  optimize: {
 AggressiveMergingPlugin: [Getter],
 AggressiveSplittingPlugin: [Getter],
 InnerGraph: [Getter],
 LimitChunkCountPlugin: [Getter],
 MinChunkSizePlugin: [Getter],
 ModuleConcatenationPlugin: [Getter],
 RealContentHashPlugin: [Getter],
 RuntimeChunkPlugin: [Getter],
 SideEffectsFlagPlugin: [Getter],
 SplitChunksPlugin: [Getter]
  },
  runtime: {
 GetChunkFilenameRuntimeModule: [Getter],
 LoadScriptRuntimeModule: [Getter]
  },
  prefetch: { ChunkPrefetchPreloadPlugin: [Getter] },
  web: {
 FetchCompileAsyncWasmPlugin: [Getter],
 FetchCompileWasmPlugin: [Getter],
 JsonpChunkLoadingRuntimeModule: [Getter],
 JsonpTemplatePlugin: [Getter]
  },
  webworker: { WebWorkerTemplatePlugin: [Getter] },
  node: {
 NodeEnvironmentPlugin: [Getter],
 NodeSourcePlugin: [Getter],
 NodeTargetPlugin: [Getter],
 NodeTemplatePlugin: [Getter],
 ReadFileCompileWasmPlugin: [Getter]
  },
  electron: { ElectronTargetPlugin: [Getter] },
  wasm: { AsyncWebAssemblyModulesPlugin: [Getter] },
  library: { AbstractLibraryPlugin: [Getter], EnableLibraryPlugin: [Getter] },
  container: {
 ContainerPlugin: [Getter],
 ContainerReferencePlugin: [Getter],
 ModuleFederationPlugin: [Getter],
 scope: [Getter]
  },
  sharing: {
 ConsumeSharedPlugin: [Getter],
 ProvideSharedPlugin: [Getter],
 SharePlugin: [Getter],
 scope: [Getter]
  },
  debug: { ProfilingPlugin: [Getter] },
  util: {
 createHash: [Getter],
 comparators: [Getter],
 runtime: [Getter],
 serialization: [Getter],
 cleverMerge: [Getter],
 LazySet: [Getter]
  },
  sources: [Getter],
  experiments: {
 schemes: { HttpUriPlugin: [Getter] },
 ids: { SyncModuleIdsPlugin: [Getter] }
  }
}

fn:

// lib/index.js
// fn懒加载出来的webpack核心函数
const fn = lazyFunction(() => require("./webpack"));

lazyFunction:这里我们可以知道webpack核心函数(lib/webpack)是怎么接受参数的

// lib/index.js
const lazyFunction = factory => {
 // 和mergeExports一样返回一个返回执行了factory后的函数
 const fac = memoize(factory); // fac函数一个函数
 const f = (
     // 这里args就是我们传入的webpack配置参数
     (...args) => {
        /** 
          * fac() 等于 factory()
          * fac()(...args) 等于 factory()(...args)
          * factory 等于 () => require("./webpack")
          * require("./webpack")(...args)
          */
         return fac()(...args); 
     }
 );
 return (f);// 最后返回f这个函数,在mergeExports里getter添加之后作为入口抛出,在前端工程化项目中webpack启动器里传入webpack配置参数执行
};

webpack.js

lib/webpack.js
抛出编译器(有callback则执行编译器编译)

webpack 方法 主方法,抛出编译器compiler(有callback则执行编译器编译)
options是一个webpack配置对象或者配置对象数组,回调函数callback,有callback就可以在这里直接编译,没有则需要外部调用compiler.run()
在《build一个weapp》的packages/taro-webpack5-runner/src/index.mini.ts中 const compiler = webpack(webpackConfig) 没有传callback只传了webpack配置,正好在webpack启动器里调用了run、watch、close方法

// lib/webpack.js
const webpack = (options, callback) => {
 // create返回一个对象对象里有编译器、是否监听、监听参数
 const create = () => {
     // options作为数组然后用webpackOptionsSchemaCheck校验参数
     if (!asArray(options).every(webpackOptionsSchemaCheck)) { // false
        ...
     }
     let compiler; // MultiCompiler多个编译器或Compiler单个编译器
     let watch = false; // 是否热更新监听,只要有一个webpack配置是热更新就监听
     let watchOptions; // 热更新监听的参数可以是对象也可以是个对象数组
     if (Array.isArray(options)) {
         compiler = createMultiCompiler(options,options);
         watch = options.some(options => options.watch);
         watchOptions = options.map(options => options.watchOptions || {});
     } else {
         const webpackOptions = options;
         compiler = createCompiler(webpackOptions);
         watch = webpackOptions.watch;
         watchOptions = webpackOptions.watchOptions || {};
     }
     return { compiler, watch, watchOptions };
 };
 if (callback) {
     try {
         const { compiler, watch, watchOptions } = create();
         if (watch) {
             // 开始监听
             compiler.watch(watchOptions, callback);
         } else {
             // 开始编译
             compiler.run((err, stats) => {
                 compiler.close(err2 => {
                     callback(err || err2, stats);
                 });
             });
         }
         // 抛出编译器
         return compiler;
     } catch (err) {
         process.nextTick(() => callback(err));
         return null;
     }
 } else {
     const { compiler, watch } = create();
     if (watch) {
         util.deprecate(
             () => {},
             "A 'callback' argument needs to be provided to the 'webpack(options, callback)' function when the 'watch' option is set. There is no way to handle the 'watch' option without a callback.",
             "DEP_WEBPACK_WATCH_WITHOUT_CALLBACK"
         )();
     }
     // 抛出编译器
     return compiler;
 }
};

createCompiler创建一个编译器

// 参数就是webpack配置参数对象
const createCompiler = rawOptions => {
 // 把webpack配置参数标准化
 const options = getNormalizedWebpackOptions(rawOptions);
 // 基础参数的初始化
 applyWebpackOptionsBaseDefaults(options);
 // 实例化Compiler编译器
 const compiler = new Compiler(options.context, options);
 // 拓展compiler,添加对文件的输入缓存、监听、输出、注册beforeRun钩子
 new NodeEnvironmentPlugin({
     infrastructureLogging: options.infrastructureLogging
 }).apply(compiler);
 // 注册plugin插件this指向编译器compiler,重新注册一遍插件
 if (Array.isArray(options.plugins)) {
     for (const plugin of options.plugins) {
         if (typeof plugin === "function") {
             plugin.call(compiler, compiler);
         } else {
             plugin.apply(compiler);
         }
     }
 }
 // 基础参数的初始化
 applyWebpackOptionsDefaults(options);
 // 执行environment钩子和afterEnvironment钩子
 compiler.hooks.environment.call(); // 获取环境信息
 compiler.hooks.afterEnvironment.call(); // 获取环境信息之后
 // 注册内置插件
 new WebpackOptionsApply().process(options, compiler);
 // 执行initialize钩子
 compiler.hooks.initialize.call(); // 初始化
 return compiler;
};

注:以下是createCompiler里的相关的函数、类的简单介绍:

applyWebpackOptionsBaseDefaults基础参数的初始化
对标准webpack配置参数context、infrastructureLogging的初始化

const applyWebpackOptionsBaseDefaults = options => {
 // context设置,如果options.context是undefined,options.context就等于项目路径
 F(options, "context", () => process.cwd());
 // 日志输出格式设置
 applyInfrastructureLoggingDefaults(options.infrastructureLogging);
};

applyWebpackOptionsDefaults基础参数的初始化
对标准webpack配置参数context、target、devtool...的初始化

const applyWebpackOptionsDefaults = options => {
  ...
};

NodeEnvironmentPlugin拓展compiler,添加对文件的输入缓存、监听、输出、注册beforeRun钩子

class NodeEnvironmentPlugin {
 constructor(options) {
     this.options = options; // webpack配置参数标准化
 }

 apply(compiler) {
     // infrastructureLogging:日志输出格式设置
     const { infrastructureLogging } = this.options;
     // 日志打印器ConsoleLogger打印器
     compiler.infrastructureLogger = createConsoleLogger({
       ...
     });
     // 输入缓存文件操作
     compiler.inputFileSystem = new CachedInputFileSystem(fs, 60000);
     // 输入文件系统
     const inputFileSystem = compiler.inputFileSystem;
     // 输出文件系统,挂载到compiler对象(graceful-fs就是对node原生fs做了一层封装本质还是node的fs)
     compiler.outputFileSystem = fs;
     compiler.intermediateFileSystem = fs;
     // 监听文件系统,挂载到compiler对象
     compiler.watchFileSystem = new NodeWatchFileSystem(
         compiler.inputFileSystem
     );
     // 注册beforeRun钩子 在执行compiler.run前执行
     compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => {
         if (compiler.inputFileSystem === inputFileSystem) {
             compiler.fsStartTime = Date.now();
             inputFileSystem.purge(); // 清除缓存
         }
     });
 }
}

WebpackOptionsApply注册内置插件

class WebpackOptionsApply extends OptionsApply {
 constructor() {
     super();
 }
 process(options, compiler) {
    // 注册内置插件
    ...
    new ExternalsPlugin(...).apply(compiler); // web 配置外部文件的模块加载
    new ChunkPrefetchPreloadPlugin().apply(compiler);
    new ArrayPushCallbackChunkFormatPlugin().apply(compiler);
    new EnableChunkLoadingPlugin(type).apply(compiler);
    new EnableWasmLoadingPlugin(type).apply(compiler);
    ...
    // 执行完成注册内置插件钩子
    compiler.hooks.afterPlugins.call(compiler);
    ...
    compiler.hooks.afterResolvers.call(compiler);
 }
}

Compiler

lib/Compiler.js将编译好的文件输出
webpack的主要引擎,在compiler对象记录了完整的webpack环境信息,在webpack从启动到结束,compiler只会生成一次。你可以在compiler对象上读取到webpack config信息,outputPath等

class Compiler {
    constructor(context, options = ({})) {
       super();
       this.hooks = Object.freeze({
          initialize: new SyncHook([]),
          shouldEmit: new SyncBailHook(["compilation"]),
          ...
       });
       this.webpack = webpack;
       ...
    }
    watch(watchOptions, handler) {
       ...
    }
    run(callback) {
       ...
    }
    purgeInputFileSystem() {
       ...
    }
    compile(callback) {
       ...
    }
    close(callback) {
       ...
    }
    
}

run

 run(callback) {
     // 重复执行报错
     if (this.running) {
         return callback(new ConcurrentCompilationError());
     }

     let logger; // 日志打印
     // 结束回调(无论执行成功还是失败)
     const finalCallback = (err, stats) => {
         ...
     };
     // 此刻时间
     const startTime = Date.now();
     // 执行过了将running变为true,用于判断是否重复执行
     this.running = true;
     const onCompiled = (err, compilation) => {
         ...
     };
     // 执行
     const run = () => {
         this.hooks.beforeRun.callAsync(this, err => {
             if (err) return finalCallback(err);

             this.hooks.run.callAsync(this, err => {
                 if (err) return finalCallback(err);

                 this.readRecords(err => {
                     if (err) return finalCallback(err);
                     // 传入onCompiled,this.compile继续执行
                     this.compile(onCompiled);
                 });
             });
         });
     };

     if (this.idle) {
         this.cache.endIdle(err => {
             if (err) return finalCallback(err);
             this.idle = false;
             run();
         });
     } else {
         run();
     }
 }

compile完成编译

 compile(callback) {
     //  参数是:普通模块工厂normalModuleFactory、上下文模块工厂contextModuleFactory
     const params = this.newCompilationParams();
     // 异步调用beforeCompile钩子
     this.hooks.beforeCompile.callAsync(params, err => {
         if (err) return callback(err);
         // 调用compile钩子
         this.hooks.compile.call(params);
         // 获取一个编译器Compilation!!!!!!
         const compilation = this.newCompilation(params);

         const logger = compilation.getLogger("webpack.Compiler");

         logger.time("make hook");
         // 入口文件开始,构建模块,直到所有模块创建结束
         // 执行make钩子会调用compilation上的addEntry
         this.hooks.make.callAsync(compilation, err => {
             logger.timeEnd("make hook");
             if (err) return callback(err);

             logger.time("finish make hook");
             this.hooks.finishMake.callAsync(compilation, err => {
                 logger.timeEnd("finish make hook");
                 if (err) return callback(err);

                 process.nextTick(() => {
                     logger.time("finish compilation");
                     // 编译完成
                     compilation.finish(err => {
                         logger.timeEnd("finish compilation");
                         if (err) return callback(err);

                         logger.time("seal compilation");
                         compilation.seal(err => {
                             logger.timeEnd("seal compilation");
                             if (err) return callback(err);

                             logger.time("afterCompile hook");
                             // 异步调用afterCompile,返回回调函数
                             this.hooks.afterCompile.callAsync(compilation, err => {
                                 logger.timeEnd("afterCompile hook");
                                 if (err) return callback(err);
                                 // 执行一些列钩子后,执行run里的onCompiled,onCompiled(null, compilation)
                                 return callback(null, compilation);
                             });
                         });
                     });
                 });
             });
         });
     });
 }

run里的onCompiled

     const onCompiled = (err, compilation) => {
         if (err) return finalCallback(err);
         //如果没有编译完成
         if (this.hooks.shouldEmit.call(compilation) === false) {
             compilation.startTime = startTime;
             compilation.endTime = Date.now();
             const stats = new Stats(compilation);
             this.hooks.done.callAsync(stats, err => {
                 if (err) return finalCallback(err);
                 return finalCallback(null, stats);
             });
             return;
         }

         process.nextTick(() => {
             logger = compilation.getLogger("webpack.Compiler");
             logger.time("emitAssets");
             // 输出编译后的文件,调用emitAsset方法,emitAsset主要负责写入文件输出文件,不影响我们先看编译(通过this.outputFileSystem、mkdirp、emitFiles输出文件)
             this.emitAssets(compilation, err => {
                 logger.timeEnd("emitAssets");
                 if (err) return finalCallback(err);
                 // 如果上传的编译没有完成则重来一遍编译
                 if (compilation.hooks.needAdditionalPass.call()) {
                     compilation.needAdditionalPass = true;

                     compilation.startTime = startTime;
                     compilation.endTime = Date.now();
                     logger.time("done hook");
                     const stats = new Stats(compilation);
                     this.hooks.done.callAsync(stats, err => {
                         logger.timeEnd("done hook");
                         if (err) return finalCallback(err);

                         this.hooks.additionalPass.callAsync(err => {
                             if (err) return finalCallback(err);
                             this.compile(onCompiled);
                         });
                     });
                     return;
                 }
                 // 完成编译
                 logger.time("emitRecords");
                 this.emitRecords(err => {
                     logger.timeEnd("emitRecords");
                     if (err) return finalCallback(err);
                     // 编译开始和编译结束时间
                     compilation.startTime = startTime;
                     compilation.endTime = Date.now();
                     logger.time("done hook");
                     const stats = new Stats(compilation);
                     this.hooks.done.callAsync(stats, err => {
                         logger.timeEnd("done hook");
                         if (err) return finalCallback(err);
                         this.cache.storeBuildDependencies(
                             compilation.buildDependencies,
                             err => {
                                 if (err) return finalCallback(err);
                                 // 编译完成返回new Stats(compilation)表示完成编译
                                 return finalCallback(null, stats);
                             }
                         );
                     });
                 });
             });
         });
     };

emitAssets收集编译好的文件之后创建文件

emitAssets(compilation, callback) {
let outputPath;
// 收集编译好的文件
const emitFiles = err => {
    const assets = compilation.getAssets();
    compilation.assets = { ...compilation.assets };
    const caseInsensitiveMap = new Map();
    const allTargetPaths = new Set();
    // 对每个file资源文件进行路径拼接,将每个source源码转换为buffer(为了性能提升),最后将文件写入真实目标路径
    asyncLib.forEachLimit(...)
    ...
}
// 创建文件
this.hooks.emit.callAsync(compilation, err => {
    if (err) return callback(err);
    outputPath = compilation.getPath(this.outputPath, {});
    mkdirp(this.outputFileSystem, outputPath, emitFiles);
  });
}

Compilation

lib/Compilation.js 所有准备就绪了现在开始进行编译
在Compiler.js实例化用于编译,代表了一次单一的版本构建和生成资源。compilation编译作业可以多次执行,比如webpack工作在watch模式下,每次监测到源文件发生变化时,都会重新实例化一个compilation对象。一个compilation对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息

在Compiler.js中用到了compilation.finish和compilation.seal以及make钩子时调用的compilation.addEntry
addEntry:启动构建入口模块,成功后将入口模块添加到程序之中
finish:完成编译,收集了每个模块构建是产生的问题
seal:生成chunks,对chunks进行一系列的优化

来自:https://segmentfault.com/a/1190000042505414

链接: https://fly63.com/article/detial/12142

浅谈Webpack打包工具的应用

webpack 在前端工程中随处可见,当前流行的 vue, react, weex 等解决方案都推崇 webpack 作为打包工具。前端工具云集的时代,这是你值得选择的之一。

一步一步webpack,webpack的学习入门

webpack是前端工程构建的一套工具,为什么一个程序称之为一套呢,是因为webpack其实是npm的一个模块,使用起来的话,这期间还需要很多其它模块来进行支持,所以我称之为一套工具。

如何写 Webpack 配置文件

本文从一个小Demo开始,通过不断增加功能来说明webpack的基本配置,只针对新手。webpack基本的配置就可以熟悉了,会引入loader,配置loader选项,会设置alias,会用plugins差不多。

WebPack中Plugins的使用和整理,以及常用的Plugins插件

Plugins是webpack的基础,我们都知道webpage的plugin是基于事件机制工作的,这样最大的好处是易于扩展。讲解如果扩展内置插件和其他插件,以及我们常用的Plugins插件

大多数项目中会用到的webpack小技巧

webpack技巧的总结:进度汇报、压缩、复数文件打包、分离app文件与第三方库文件、资源映射、输出css文件、开发模式、分析包的大小、更小的react项目、更小的Lodash、引入文件夹中所有文件、清除extract-text-webpack-plugin日志。

优化Webpack构建性能的几点建议

Webpack 作为目前最流行的前端构建工具之一,在 vue/react 等 Framework 的生态圈中都占据重要地位。在开发现代 Web 应用的过程中,Webpack 和我们的开发过程和发布过程都息息相关,如何改善 Webpack 构建打包的性能也关系到我们开发和发布部署的效率。

Webpack 4正式发布了!

新版 Webpack 中我们所做的每一个更新目的都在于此,为了当大家在使用 Webpack 的时候敏捷连续毫无顿挫感。 webpack 4 进行构建性能测试,得出的结果非常有趣。结果很惊人,构建时间降低了 60%-98%!

Webpack 4.0.0不再支持 Node.js 4

Webpack 是一个现代 JavaScript 应用程序的模块打包器 (module bundler) 。当 Webpack 处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块

我当初为什么写webpack_Tobias Koppers

Tobias Koppers是一位自由软件开发者,家住德国纽伦堡。他因写出webpack这个已有数百万开发者使用的开源软件而名噪一时。他目前专注于JavaScript和开源项目。以下是我对他个人的专访,希望对大家有所启发。

webpack项目轻松混用css module

本文讲述css-loader开启css模块功能之后,如何与引用的npm包中样式文件不产生冲突。比如antd-mobilenpm包的引入。在不做特殊处理的前提下,样式文件将会被转译成css module。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!