webpack原理探究 && 打包优化
在做vue项目和react项目时,都用到了webpack。webpack帮助我们很好地提高了工作效率,但是一直以来没有对其原理进行探究,略有遗憾。 因为使用一个工具,能够深入了解其原理才能更好地使用。 这篇文章将大致分为三个部分进行解读:
- webpack打包简单介绍
- 输入webpack后发生了什么,整个运行机制大致是怎样的?
- 如何理解打包出的bundle.js?
- 如何实现一个简单的webpack打包工具?
- 打包优化
第一部分: webpack打包简单介绍
当一个项目使用webpack打包时,webpack会认为所有的文件都是模块,并将其打包到一个文件中。 但是webpack只能识别js文件,所以对于其他文件,我们需要使用loader来完成打包。
通过webpack打包,我们能很好地解决前端项目中的依赖问题,这样可以帮助我们专注于实现项目的代码逻辑,而非是依赖、命名冲突等。
第二部分: 输入webpack后发生了什么, 整个运行机制大致是怎样的?
一般情况下,我们都会在根目录下配置一个 webpack.config.js 文件,用于配置webpack打包。 当我们打开控制台时,输入webpack, 就会根据配置文件对项目进行打包了。但是,在这个过程中究竟发生了什么呢?
执行脚本 bin/webpack.js
当在cmd中输入一个命令执行时,实际上执行的都是一个类似于可执行的二进制文件,比如执行node命令、ping命令时都是这样的, 在项目的node_modules下的webpack根目录下找到package.json, 可以看到下面的一个kv:
"bin": {
"webpack": "./bin/webpack.js"
},
这就说明在执行二进制文件时,会运行 ./bin/webpack.js文件,找到这个文件,我们可以看到主要的代码如下:
// 引入nodejs的path模块
var path = require("path"); // 获取 /bin/webpack.js的绝对路径
try {
var localWebpack = require.resolve(path.join(process.cwd(), "node_modules", "webpack", "bin", "webpack.js"));
if(__filename !== localWebpack) {
return require(localWebpack);
}
} catch(e) {} // 引入yargs模块,用于处理命令行参数
var yargs = require("yargs")
.usage("webpack " + require("../package.json").version + "\n" +
"Usage: https://webpack.js.org/api/cli/\n" +
"Usage without config file: webpack <entry> [<entry>] <output>\n" +
"Usage with config file: webpack"); // 使用yargs来初始化命令行对象
require("./config-yargs")(yargs); var DISPLAY_GROUP = "Stats options:";
var BASIC_GROUP = "Basic options:"; // 命令行参数的基本配置
yargs.options({
"json": {
type: "boolean",
alias: "j",
describe: "Prints the result as JSON."
},
"progress": {
type: "boolean",
describe: "Print compilation progress in percentage",
group: BASIC_GROUP
},
// 省略若干
}); // yargs模块提供的argv对象,用来读取命令行参数,alias可以设置某个命令的简称,方便输入。
var argv = yargs.argv; if(argv.verbose) {
argv["display"] = "verbose";
} // argv为读取命令行的参数,通过conver-argv配置文件将命令行中的参数经过处理保存在options对象中
var options = require("./convert-argv")(yargs, argv); function ifArg(name, fn, init) {
if(Array.isArray(argv[name])) {
if(init) init();
argv[name].forEach(fn);
} else if(typeof argv[name] !== "undefined") {
if(init) init();
fn(argv[name], -);
}
} // /bin/webpack.js的核心函数
function processOptions(options) { // 支持promise风格的异步回调
if(typeof options.then === "function") {
options.then(processOptions).catch(function(err) {
console.error(err.stack || err);
process.exit(); // eslint-disable-line
});
return;
} // 得到webpack编译对象时数组情况下的options
var firstOptions = [].concat(options)[];
var statsPresetToOptions = require("../lib/Stats.js").presetToOptions; // 设置输出option
var outputOptions = options.stats;
if(typeof outputOptions === "boolean" || typeof outputOptions === "string") {
outputOptions = statsPresetToOptions(outputOptions);
} else if(!outputOptions) {
outputOptions = {};
} // 省略若干。。。。。 // 引入主入口模块 /lib/webpack.js
var webpack = require("../lib/webpack.js"); var compiler;
try { // 使用webpack函数开始对获得的配置对象进行编译, 返回compiler
compiler = webpack(options);
} catch(e) {
// 省略若干。。。
} function compilerCallback(err, stats) {
// 编译完成之后的回调函数
} // 如果有watch配置,则及时进行编译。
if(firstOptions.watch || options.watch) {
var watchOptions = firstOptions.watchOptions || firstOptions.watch || options.watch || {};
if(watchOptions.stdin) {
process.stdin.on("end", function() {
process.exit(); // eslint-disable-line
});
process.stdin.resume();
}
compiler.watch(watchOptions, compilerCallback);
console.log("\nWebpack is watching the files…\n");
} else
compiler.run(compilerCallback); } // 处理这些配置选项,即调用上面的函数
processOptions(options);
实际上上面的这段代码还是比较好理解的,就是使用相关模块获取到配置对象,然后从./lib/webpack.js 中获取到webpack来进行编译, 然后根据配置选项进行相应的处理。 这里比较重要的就是webpack.js函数,我们来看看源码。
./lib/webpack.js解析
// 建立webpack主函数,下面某些代码被省略了。
function webpack(options, callback) { let compiler;
if(Array.isArray(options)) {
// 如果webapck是一个数组,则一次执行
compiler = new MultiCompiler(options.map(options => webpack(options)));
} else if(typeof options === "object") { // 一般情况下webpack配置应该是一个对象,使用默认的处理配置中的所有选项
new WebpackOptionsDefaulter().process(options);
// 实例化一个 Compiler,Compiler 会继承一个 Tapable 插件框架
// Compiler 实例化后会继承到 apply、plugin 等调用和绑定插件的方法
compiler = new Compiler(); compiler.context = options.context;
compiler.options = options;
new NodeEnvironmentPlugin().apply(compiler);
if(options.plugins && Array.isArray(options.plugins)) {
// 对于选项中的插件,进行使用、编译
compiler.apply.apply(compiler, options.plugins);
}
compiler.applyPlugins("environment");
compiler.applyPlugins("after-environment");
compiler.options = new WebpackOptionsApply().process(options, compiler);
} else {
throw new Error("Invalid argument: options");
} return compiler;
}
exports = module.exports = webpack;
注意:
一是 Compiler,实例化它会继承 Tapable ,这个 Tapable 是一个插件框架,通过继承它的一系列方法来实现注册和调用插件,我们可以看到在 webpack 的源码中,存在大量的 compiler.apply、compiler.applyPlugins、compiler.plugin 等Tapable方法的调用。Webpack 的 plugin 注册和调用方式,都是源自 Tapable 。Webpack 通过 plugin 的 apply 方法安装该 plugin,同时传入一个 webpack 编译对象(Webpack compiler object)。
二是 WebpackOptionsApply 的实例方法 process (options, compiler),这个方法将会针对我们传进去的webpack 编译对象进行逐一编译,接下来我们再来仔细看看这个模块。
调用 lib/WebpackOptionsApply.js
模块的 process
方法来逐一编译 webpack 编译对象的各项(这里的文件才是比较核心的)
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict"; // 这里引入了若干插件(数十个) // 给webpack中的配置对象使用插件
class WebpackOptionsApply extends OptionsApply {
constructor() {
super();
} // 处理配置独享主要函数
process(options, compiler) {
let ExternalsPlugin;
// 根据options来配置options
compiler.outputPath = options.output.path;
compiler.recordsInputPath = options.recordsInputPath || options.recordsPath;
compiler.recordsOutputPath = options.recordsOutputPath || options.recordsPath;
compiler.name = options.name;
compiler.dependencies = options.dependencies;
if(typeof options.target === "string") {
let JsonpTemplatePlugin;
let NodeSourcePlugin;
let NodeTargetPlugin;
let NodeTemplatePlugin; switch(options.target) {
case "web":
// 省略处理代码
case "webworker":
// 省略处理代码
case "node":
case "async-node":
// 省略处理代码
break;
case "node-webkit":
// 省略处理代码
break;
case "atom":
case "electron":
case "electron-main":
// 省略处理代码
case "electron-renderer":
// 省略处理代码
default:
throw new Error("Unsupported target '" + options.target + "'.");
}
} else if(options.target !== false) {
options.target(compiler);
} else {
throw new Error("Unsupported target '" + options.target + "'.");
} // 根据配置来决定是否生成sourcemap
if(options.devtool && (options.devtool.indexOf("sourcemap") >= || options.devtool.indexOf("source-map") >= )) {
// 省略若干
// sourcemap代码下通常都会指明源地址
comment = legacy && modern ? "\n/*\n//@ source" + "MappingURL=[url]\n//# source" + "MappingURL=[url]\n*/" :
legacy ? "\n/*\n//@ source" + "MappingURL=[url]\n*/" :
modern ? "\n//# source" + "MappingURL=[url]" :
null;
let Plugin = evalWrapped ? EvalSourceMapDevToolPlugin : SourceMapDevToolPlugin;
compiler.apply(new Plugin({
filename: inline ? null : options.output.sourceMapFilename,
moduleFilenameTemplate: options.output.devtoolModuleFilenameTemplate,
fallbackModuleFilenameTemplate: options.output.devtoolFallbackModuleFilenameTemplate,
append: hidden ? false : comment,
module: moduleMaps ? true : cheap ? false : true,
columns: cheap ? false : true,
lineToLine: options.output.devtoolLineToLine,
noSources: noSources,
}));
} else if(options.devtool && options.devtool.indexOf("eval") >= ) {
legacy = options.devtool.indexOf("@") >= ;
modern = options.devtool.indexOf("#") >= ;
comment = legacy && modern ? "\n//@ sourceURL=[url]\n//# sourceURL=[url]" :
legacy ? "\n//@ sourceURL=[url]" :
modern ? "\n//# sourceURL=[url]" :
null;
compiler.apply(new EvalDevToolModulePlugin(comment, options.output.devtoolModuleFilenameTemplate));
} compiler.apply(
new CompatibilityPlugin(),
// 使用相关插件进行处理
); return options;
}
} module.exports = WebpackOptionsApply;
不出意外,这个构造函数被实例化后会返回一个对象。 然后由compiler处理
到这基本上就是大致流程了,我们可以再介绍上一步中的常用的插件:UglifyJsPlugin.js
lib/optimize/UglifyJsPlugin.js // 引入一些依赖,主要是与压缩代码、sourceMap 相关
var SourceMapConsumer = require("webpack-core/lib/source-map").SourceMapConsumer;
var SourceMapSource = require("webpack-core/lib/SourceMapSource");
var RawSource = require("webpack-core/lib/RawSource");
var RequestShortener = require("../RequestShortener");
var ModuleFilenameHelpers = require("../ModuleFilenameHelpers");
var uglify = require("uglify-js"); // 定义构造器函数
function UglifyJsPlugin(options) {
...
}
// 将构造器暴露出去
module.exports = UglifyJsPlugin; // 按照 Tapable 风格编写插件
UglifyJsPlugin.prototype.apply = function(compiler) {
...
// 编译器开始编译
compiler.plugin("compilation", function(compilation) {
...
// 编译器开始调用 "optimize-chunk-assets" 插件编译
compilation.plugin("optimize-chunk-assets", function(chunks, callback) {
var files = [];
...
files.forEach(function(file) {
...
try {
var asset = compilation.assets[file];
if(asset.__UglifyJsPlugin) {
compilation.assets[file] = asset.__UglifyJsPlugin;
return;
}
if(options.sourceMap !== false) {
// 需要 sourceMap 时要做的一些操作...
} else {
// 获取读取到的源文件
var input = asset.source();
...
}
// base54 编码重置
uglify.base54.reset();
// 将源文件生成语法树
var ast = uglify.parse(input, {
filename: file
});
// 语法树转换为压缩后的代码
if(options.compress !== false) {
ast.figure_out_scope();
var compress = uglify.Compressor(options.compress); // eslint-disable-line new-cap
ast = ast.transform(compress);
}
// 处理混淆变量名
if(options.mangle !== false) {
ast.figure_out_scope();
ast.compute_char_frequency(options.mangle || {});
ast.mangle_names(options.mangle || {});
if(options.mangle && options.mangle.props) {
uglify.mangle_properties(ast, options.mangle.props);
}
}
// 定义输出变量名
var output = {};
// 处理输出的注释
output.comments = Object.prototype.hasOwnProperty.call(options, "comments") ? options.comments : /^\**!|@preserve|@license/;
// 处理输出的美化
output.beautify = options.beautify;
for(var k in options.output) {
output[k] = options.output[k];
}
// 处理输出的 sourceMap
if(options.sourceMap !== false) {
var map = uglify.SourceMap({ // eslint-disable-line new-cap
file: file,
root: ""
});
output.source_map = map; // eslint-disable-line camelcase
}
// 将压缩后的数据输出
var stream = uglify.OutputStream(output); // eslint-disable-line new-cap
ast.print(stream);
if(map) map = map + "";
stream = stream + "";
asset.__UglifyJsPlugin = compilation.assets[file] = (map ?
new SourceMapSource(stream, file, JSON.parse(map), input, inputSourceMap) :
new RawSource(stream));
if(warnings.length > ) {
compilation.warnings.push(new Error(file + " from UglifyJs\n" + warnings.join("\n")));
}
} catch(err) {
// 处理异常
...
} finally {
...
}
});
// 回调函数
callback();
});
compilation.plugin("normal-module-loader", function(context) {
context.minimize = true;
});
});
};
现在我们回过头来再看看整体流程,当我们在命令行输入 webpack 命令,按下回车时都发生了什么:
- 执行 bin 目录下的 webpack.js 脚本,解析命令行参数以及开始执行编译。
- 调用 lib 目录下的 webpack.js 文件的核心函数 webpack ,实例化一个
Compiler
,继承 Tapable 插件框架,实现注册和调用一系列插件。 - 调用 lib 目录下的
/WebpackOptionsApply.js
模块的process
方法,使用各种各样的插件来逐一编译 webpack 编译对象的各项。 - 在3中调用的各种插件编译并输出新文件。
第三部分:如何理解打包出的bundle.js?
一个入口文件
// webpack.config.js
module.exports = {
entry: ["./index.js"],
output: {
path: __dirname + "/dist",
filename: "bundle.js"
},
watch: true,
module: {
loaders: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react']
}
},
{
test: /\.css$/,
loader: 'style-loader!css-loader'
},
{
test: /\.less$/,
use: [{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "less-loader" // compiles Less to CSS
}]
},
{
test: /\.(jpg|png|svg)$/,
loader: 'url-loader'
}
]
}
} // index.js
import React from "react";
import ReactDom from 'react-dom'
import App from './pages/app.jsx'
ReactDom.render(
<App/>,
document.querySelector('#app')
)
// bundle.js
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = );
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports) { console.log('index'); /***/ },
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
function reactProdInvariant(code) {
var argCount = arguments.length - 1;
var message = 'Minified React error #' + code + '; visit ' + 'http://facebook.github.io/react/docs/error-decoder.html?invariant=' + code;
for (var argIdx = 0; argIdx < argCount; argIdx++) {
message += '&args[]=' + encodeURIComponent(arguments[argIdx + 1]);
}
message += ' for the full message or use the non-minified dev environment' + ' for full errors and additional helpful warnings.';
var error = new Error(message);
error.name = 'Invariant Violation';
error.framesToPop = 1; // we don't care about reactProdInvariant's own frame
throw error;
}
module.exports = reactProdInvariant;
/***/ }),
// 省略若干。。。。
/******/ ]);
- 可以看到,真个bundle.js是一个自执行函数,前65行都在定义这个自执行函数,最后传入了一个数组作为参数,因为只有一个js文件,这里的数组长度为1,并且数组里的每一个元素都是一个自执行函数,自执行函数中包含着index.js里的内容。
- 即整个bundle.js文件是一个传入了 包含若干个模块的数组 作为参数,即传入的modules是一个数组。
- 在这个bundle.js文件中的自执行函数中定义了一个webpack打包的函数 __webpack_require__, 这个函数式一个打包的核心函数, 接收一个moduleId作为参数,moduleId是一个数字,实际上就是整个自执行函数接收的数组参数的index值。 即整个传入的module数组,每一个元素都是一个module,我们为之定义一个特定的moduleId,进入函数,首先判断要加载的模块是否已经存在,如果已经存在, 就直接返回installedModules[moduleId].exports,这样就保证了所有的模块只会被加载一次,而不会被多次加载。 如果说这个模块还没有被加载,那么我们就创建一个installedModules[moduleId], 他是一个对象,包括i属性(即moduleId),l属性(表示这个模块是否已经被加载, 初始化为false), exports 属性它的内容是每个模块想要导出的内容, 接下来执行 modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 函数进行调用,那么这个函数具体是如何执行的呢? 首先保证在module.exports上进行调用这个函数,然后传入了module参数,即我们想要调用的这个模块,传入module.exports ,那么在每一个模块中使用的module和module.exports就都是属于这个模块的了, 同时再传入 __webpack_require__这样我们就可以在每一个模块中继续使用了加载器了,最后,导出这个模块。 调用完成之后,将l设置为true,表示已经加载,最后导出module.exports,即导出加载到的模块。
- 在自执行函数的末尾我们可以看到这个自执行函数最终返回了一个 __webpack_require__ 调用,也就是说返回了一个模块,因为__webpck_require__函数本身就会返回一个模块。 并且这个 __webpack_require__调用接收的参数是一个 moduleId ,且指明了其值为86。 也就是说入口文件的 moduleId 为86, 我们来看一看模块 86 的内容是什么。即在这个bundle.js函数执行之后,实际上得到的第一部分内容是 86 模块的内容。
/* 86 */
/***/ (function(module, exports, __webpack_require__) { module.exports = __webpack_require__(); /***/ }),
模块86非常简单,就是首先通过 __webpack_require__(87) 引入了 moduleId 为87的模块, 然后我们看看87模块是什么。
/* 87 */
/***/ (function(module, exports, __webpack_require__) { "use strict"; var _react = __webpack_require__(); var _react2 = _interopRequireDefault(_react); var _reactDom = __webpack_require__(); var _reactDom2 = _interopRequireDefault(_reactDom); var _app = __webpack_require__(); var _app2 = _interopRequireDefault(_app); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } _reactDom2.default.render(_react2.default.createElement(_app2.default, null), document.querySelector('#app')); /***/ }),
在这一部分的开头,我们也看到了index.js的内容,主要任务就是引入了 react 、react-dom、引入了App组件、最后进行渲染。 同样地,这里我们可以看到,在这个模块中,通过 __webpack_reuqire__(9) 引入了_react(这里的react添加了下划线,表示这里的react是没有对外暴露的), 然后使用_interopRequireDefault这个函数处理 --- 首先判断引入的是否是一个对象并且同时满足这个对象是否满足es6中的module导出,如果满足,就直接返回这个对象,如果不满足, 就返回一个值为obj的对象来进一步处理。 最后一步就是使用引入的各个方法来讲 App 模块挂载到 id为app为的元素下。 到这里,可以看出引入了多个模块,我们下面分别分析 __webpack_require__(9) 的react模块以及__webpack_require__(189) 的 app 模块,即一个是从外部定义的模块,一个是我们自己写的模块。这两个类型不同的模块有了区分之后,我们就可以大致理清楚整个 bundle.js 的脉络了。
__webpack_require__(9)
/* 9 */
/***/ (function(module, exports, __webpack_require__) { "use strict"; module.exports = __webpack_require__(); /***/ }),
进入了__webpack_require__(9)模块我们看到,我们需要去寻找 19 模块。 下面我们看看19模块。
/* 19 */
/***/ (function(module, exports, __webpack_require__) { "use strict";
// 这里说明了react是从外部注入的。
/* WEBPACK VAR INJECTION */(function(process) {/** // 下面的这几行和我们直接打开react.js代码的前几行是一样的,说明这些代码确实是直接引入的。
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/ var _assign = __webpack_require__(); var ReactBaseClasses = __webpack_require__();
var ReactChildren = __webpack_require__();
var ReactDOMFactories = __webpack_require__();
var ReactElement = __webpack_require__();
var ReactPropTypes = __webpack_require__();
var ReactVersion = __webpack_require__(); var createReactClass = __webpack_require__();
var onlyChild = __webpack_require__(); var createElement = ReactElement.createElement;
var createFactory = ReactElement.createFactory;
var cloneElement = ReactElement.cloneElement; if (process.env.NODE_ENV !== 'production') {
var lowPriorityWarning = __webpack_require__();
var canDefineProperty = __webpack_require__();
var ReactElementValidator = __webpack_require__();
var didWarnPropTypesDeprecated = false;
createElement = ReactElementValidator.createElement;
createFactory = ReactElementValidator.createFactory;
cloneElement = ReactElementValidator.cloneElement;
} var __spread = _assign;
var createMixin = function (mixin) {
return mixin;
}; if (process.env.NODE_ENV !== 'production') {
var warnedForSpread = false;
var warnedForCreateMixin = false;
__spread = function () {
lowPriorityWarning(warnedForSpread, 'React.__spread is deprecated and should not be used. Use ' + 'Object.assign directly or another helper function with similar ' + 'semantics. You may be seeing this warning due to your compiler. ' + 'See https://fb.me/react-spread-deprecation for more details.');
warnedForSpread = true;
return _assign.apply(null, arguments);
}; createMixin = function (mixin) {
lowPriorityWarning(warnedForCreateMixin, 'React.createMixin is deprecated and should not be used. ' + 'In React v16.0, it will be removed. ' + 'You can use this mixin directly instead. ' + 'See https://fb.me/createmixin-was-never-implemented for more info.');
warnedForCreateMixin = true;
return mixin;
};
} var React = {
// Modern Children: {
map: ReactChildren.map,
forEach: ReactChildren.forEach,
count: ReactChildren.count,
toArray: ReactChildren.toArray,
only: onlyChild
}, Component: ReactBaseClasses.Component,
PureComponent: ReactBaseClasses.PureComponent, createElement: createElement,
cloneElement: cloneElement,
isValidElement: ReactElement.isValidElement, // Classic PropTypes: ReactPropTypes,
createClass: createReactClass,
createFactory: createFactory,
createMixin: createMixin, // This looks DOM specific but these are actually isomorphic helpers
// since they are just generating DOM strings.
DOM: ReactDOMFactories, version: ReactVersion, // Deprecated hook for JSX spread, don't use this for anything.
__spread: __spread
}; if (process.env.NODE_ENV !== 'production') {
var warnedForCreateClass = false;
if (canDefineProperty) {
Object.defineProperty(React, 'PropTypes', {
get: function () {
lowPriorityWarning(didWarnPropTypesDeprecated, 'Accessing PropTypes via the main React package is deprecated,' + ' and will be removed in React v16.0.' + ' Use the latest available v15.* prop-types package from npm instead.' + ' For info on usage, compatibility, migration and more, see ' + 'https://fb.me/prop-types-docs');
didWarnPropTypesDeprecated = true;
return ReactPropTypes;
}
}); Object.defineProperty(React, 'createClass', {
get: function () {
lowPriorityWarning(warnedForCreateClass, 'Accessing createClass via the main React package is deprecated,' + ' and will be removed in React v16.0.' + " Use a plain JavaScript class instead. If you're not yet " + 'ready to migrate, create-react-class v15.* is available ' + 'on npm as a temporary, drop-in replacement. ' + 'For more info see https://fb.me/react-create-class');
warnedForCreateClass = true;
return createReactClass;
}
});
} // React.DOM factories are deprecated. Wrap these methods so that
// invocations of the React.DOM namespace and alert users to switch
// to the `react-dom-factories` package.
React.DOM = {};
var warnedForFactories = false;
Object.keys(ReactDOMFactories).forEach(function (factory) {
React.DOM[factory] = function () {
if (!warnedForFactories) {
lowPriorityWarning(false, 'Accessing factories like React.DOM.%s has been deprecated ' + 'and will be removed in v16.0+. Use the ' + 'react-dom-factories package instead. ' + ' Version 1.0 provides a drop-in replacement.' + ' For more info, see https://fb.me/react-dom-factories', factory);
warnedForFactories = true;
}
return ReactDOMFactories[factory].apply(ReactDOMFactories, arguments);
};
});
} module.exports = React;
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__())) /***/ }),
这就是react.js的核心代码,但是为什么一共就100行左右的代码呢? 这里应该引入了整个 react 文件啊。 我们从内部代码可以看到,在react模块中同样又使用了 __webpack_require__ 来引入了更多的文件, 这时因为react.js本身就是这么引入的文件的, https://unpkg.com/react@15.6.1/dist/react.js, 从源码上可以看到, 它采用的也是分块的模式,所以在webpack打包的时候,自然也是使用一个一个模块的形式进行打包引入了。 这样做的好处是什么呢? 因为这样可以增加代码的重用,就19模块的 var ReactBaseClasses = __webpack_require__(53); 而言, 即react的 ReactBaseClasses 模块需要使用,另外,在19模块的createReactClass也是需要的,它先引入了100模块,然后又引入了 19 模块。 并且对于大型的框架、库而言,都是需要按照模块进行编写的,不可能直接写在一个模块中。 react的19模块就介绍到这里。
下面我们再看看189的App模块。(这个模块是jsx文件,所以需要通过babel-loader进行转译)
/* 189 */
/***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", {
value: true
}); var _createClass = function () { function defineProperties(target, props) { for (var i = ; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = __webpack_require__(); var _react2 = _interopRequireDefault(_react); var _title = __webpack_require__(); var _title2 = _interopRequireDefault(_title); var _item = __webpack_require__(); var _item2 = _interopRequireDefault(_item); var _experience = __webpack_require__(); var _experience2 = _interopRequireDefault(_experience); var _skill = __webpack_require__(); var _skill2 = _interopRequireDefault(_skill); var _personal = __webpack_require__(); var _personal2 = _interopRequireDefault(_personal); var _intro = __webpack_require__(); var _intro2 = _interopRequireDefault(_intro); var _others = __webpack_require__(); var _others2 = _interopRequireDefault(_others); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } __webpack_require__(); var App = function (_React$Component) {
_inherits(App, _React$Component); function App() {
_classCallCheck(this, App); return _possibleConstructorReturn(this, (App.__proto__ || Object.getPrototypeOf(App)).apply(this, arguments));
} _createClass(App, [{
key: 'render',
value: function render() {
return _react2.default.createElement(
'div',
{ className: 'app-wrap' },
_react2.default.createElement(
'div',
{ className: 'sub' },
_react2.default.createElement(
'div',
{ className: 'intro' },
_react2.default.createElement(_intro2.default, null)
),
_react2.default.createElement(
'div',
{ className: 'others' },
_react2.default.createElement(_others2.default, null)
)
),
_react2.default.createElement(
'div',
{ className: 'main' },
_react2.default.createElement(
'div',
{ className: 'experience' },
_react2.default.createElement(_experience2.default, null)
),
_react2.default.createElement(
'div',
{ className: 'skill' },
_react2.default.createElement(_skill2.default, null)
),
_react2.default.createElement(
'div',
{ className: 'personal' },
_react2.default.createElement(_personal2.default, null)
)
)
);
}
}]); return App;
}(_react2.default.Component); exports.default = App; /***/ }),
而下面是app.jsx 的源代码:
import React from "react"; import Title from '../components/title.jsx'
import Item2 from '../components/item2.jsx'
import Experience from '../components/experience.jsx'
import Skill from '../components/skill.jsx'
import Personal from '../components/personal.jsx'
import Intro from '../components/intro.jsx'
import Others from '../components/others.jsx' require('../css/app.less') class App extends React.Component{ render () {
return (
<div className='app-wrap'>
<div className="sub">
<div className="intro">
<Intro/>
</div>
<div className="others">
<Others/>
</div>
</div>
<div className="main">
<div className="experience">
<Experience/>
</div>
<div className="skill">
<Skill/>
</div>
<div className="personal">
<Personal/>
</div>
</div>
</div>
)
}
} export default App;
在模块的开始,我们就看到这个模块的 _esModule 就被定义为了 true,那么代表这个模块是符合 es6 的module规范的,这样我们就可以直接导入导出了。
接下来,我们又看到了 var _react = __webpack_require__(9); 因为我们在这个文件中引入了 react 模块,但是在bundle.js最开始定义模块的时候我们知道,只要加载了一次,这个模块就会被放在 installedModules 对象中,这样,我们就可以在第二次及以后使用的过程中,直接返回 installedModules 的这个模块,而不需要重新加载了。
app模块下的app.less
接着又引入了一些依赖和更底层的组件(不是只嵌套组件的组件),比如,在 app.jsx 中我又引入了 app.less 这个less组件, 在模块189中,我们可以看到确实有一个单独引入的less组件, __webpack_require__(214); (稍后我们看看这个模块)
最后开始创建app组件,最后返回这个组件。
模块 214 (一个less模块)
/* 214 */
/***/ (function(module, exports, __webpack_require__) { // style-loader: Adds some css to the DOM by adding a <style> tag // load the styles
var content = __webpack_require__();
if(typeof content === 'string') content = [[module.i, content, '']];
// Prepare cssTransformation
var transform; var options = {}
options.transform = transform
// add the styles to the DOM
var update = __webpack_require__()(content, options);
if(content.locals) module.exports = content.locals;
// Hot Module Replacement
if(false) {
// When the styles change, update the <style> tags
if(!content.locals) {
module.hot.accept("!!../node_modules/css-loader/index.js!../node_modules/less-loader/dist/cjs.js!./app.less", function() {
var newContent = require("!!../node_modules/css-loader/index.js!../node_modules/less-loader/dist/cjs.js!./app.less");
if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
update(newContent);
});
}
// When the module is disposed, remove the <style> tags
module.hot.dispose(function() { update(); });
} /***/ }),
在这个模块中,我们可以看到这里首先提到使用 style-loader 将css添加到html中。 接着开始加载 style ,即 215 模块(css代码),然后判断 content 是否是一个字符串,如果是,就创建一个数组,包含这个字符串, 接下来, 使用热更新机制。 这里最重要的就是18模块,将css代码添加到html中,这个模块中的的核心函数为 addStylesToDom , 如下所示:
function addStylesToDom (styles, options) {
for (var i = ; i < styles.length; i++) {
var item = styles[i];
var domStyle = stylesInDom[item.id]; if(domStyle) {
domStyle.refs++; for(var j = ; j < domStyle.parts.length; j++) {
domStyle.parts[j](item.parts[j]);
} for(; j < item.parts.length; j++) {
domStyle.parts.push(addStyle(item.parts[j], options));
}
} else {
var parts = []; for(var j = ; j < item.parts.length; j++) {
parts.push(addStyle(item.parts[j], options));
} stylesInDom[item.id] = {id: item.id, refs: , parts: parts};
}
}
}
即接收两个参数,第一个就是将要添加的style,第二个就是一些选项, 内部对所有的style进行遍历, 然后添加进入。
我们可以看到215模块如下所示:
/* 215 */
/***/ (function(module, exports, __webpack_require__) { exports = module.exports = __webpack_require__()(undefined);
// imports // module
exports.push([module.i, "div.app-wrap {\n width: 80%;\n margin: 0 auto;\n overflow: hidden;\n margin-top: 10px;\n border: thin solid #ccc;\n}\ndiv.app-wrap div.sub {\n box-shadow: 0 0 10px gray;\n float: left;\n width: 35%;\n}\ndiv.app-wrap div.sub div.intro {\n margin-bottom: 63px;\n}\ndiv.app-wrap div.main {\n float: right;\n width: 63%;\n margin-right: 5px;\n}\ndiv.app-wrap div.main div.skill {\n margin-bottom: 10px;\n}\n", ""]); // exports /***/ })
即这里首先引入了 17 模块, 17模块的作用是通过css-loader注入基础代码(这个基础css代码是一个数组), 接着再push进入我写的app.less代码(注意:这里的css代码已经被less-loader转化为了css代码), 然后进行注入的,最后是导出的这个css代码。
app模块下的introl.jsx模块(203模块)
这个模块的jsx代码如下:
import React from "react"
require('../css/intro.less')
import protrait from '../images/portrait.png' class Intro extends React.Component{
render () { return (
<div className='intro-wrap'>
<div className="portrait">
<img src={protrait}/>
</div>
<div className="name">WayneZhu</div>
<div className="position">
<span>
前端开发工程师
</span>
</div>
</div>
)
}
} export default Intro;
选用这个模块的目的是因为这里有一个导入图片的步骤,这样,我们就可以观察图片的打包过程了。
下面是bundle.js中的该模块:
/* 203 */
/***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", {
value: true
}); var _createClass = function () { function defineProperties(target, props) { for (var i = ; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = __webpack_require__(); var _react2 = _interopRequireDefault(_react); var _portrait = __webpack_require__(); var _portrait2 = _interopRequireDefault(_portrait); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } __webpack_require__(); var Intro = function (_React$Component) {
_inherits(Intro, _React$Component); function Intro() {
_classCallCheck(this, Intro); return _possibleConstructorReturn(this, (Intro.__proto__ || Object.getPrototypeOf(Intro)).apply(this, arguments));
} _createClass(Intro, [{
key: 'render',
value: function render() { return _react2.default.createElement(
'div',
{ className: 'intro-wrap' },
_react2.default.createElement(
'div',
{ className: 'portrait' },
_react2.default.createElement('img', { src: _portrait2.default })
),
_react2.default.createElement(
'div',
{ className: 'name' },
'WayneZhu'
),
_react2.default.createElement(
'div',
{ className: 'position' },
_react2.default.createElement(
'span',
null,
'\u524D\u7AEF\u5F00\u53D1\u5DE5\u7A0B\u5E08'
)
)
);
}
}]); return Intro;
}(_react2.default.Component); exports.default = Intro; /***/ }),
在这个模块中,我们可以看到webpack将图片也当做了一个模块204,然后引入了这个模块,最后直接在 图片的src中引用, 所以我们有必要看看 204 模块的内容。
204模块(png图片)
这个模块很简单,就是将图片进行了base64编码,得到的结果如下所示:
/* 204 */
/***/ (function(module, exports) { // 下面的编码内容省略了大部分
module.exports = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYUAAAFRCAYAAACbjLFxAAACC" /***/ }),
这样,就可以直接将这个编码当做src,而不会发出请求来当做http请求了。
当然并不是所有的图片都会被当做 模块 进行打包, 我们完全可以去请求一个本地资源, 但是对于本地资源,我们需要提前进行设置, 一般,需要在node的服务器文件中添加下面的代码:
// node你服务器使用的静态文件
app.use('/', express.static('./www'))
这样,我们就可以发现,在使用图片时,可以直接是:
<img src='/images/add.png' className='create' onClick={this.createRoom}/>
即这里 /images/add.png 默认是在 www 这个文件夹下的,因为在node中,我们已经设置了静态文件的位置了。 这样,webpack 也只是引用,而不会将至转化为base64编码:
_react2.default.createElement(
"div",
{ className: "channel" },
_react2.default.createElement(
"span",
null,
"\u6240\u6709\u623F\u95F4"
),
_react2.default.createElement("img", { src: "/images/add.png", className: "create", onClick: this.createRoom })
),
这样,我们就可以发现: 这里直接使用的就是路径,引用 www 文件夹下的文件。 当然,我们也可以把www下的文件直接以模块的形式打包进来。 但是,在使用静态文件时,我们只能使用 www 下这个制定文件夹下的文件,而不能使用其他文件夹下的文件。
可以发现的是,在寻找文件的过程中,采用的是深度优先的遍历原则。
ok! bundle.js 的内容到这里大致就比较清楚了。下面,我们尝试着实现一个简单的webpack打包工具吧。
第四部分: 如何实现一个简单的webpack打包工具?
前言:
一个webpack工具是需要很大的时间和精力来创造的,我们不可能实现所有的功能,这里只是提供一个大体的思路,完成最简单的功能,如实现使用符合commonjs规范的几个文件打包为一个文件。
当然,浏览器是没有办法执行commonjs规范的js文件的,所以,我们需要写成自执行函数的形式,就像webpack打包出来的bundle.js一样。
需求:
我们实现的需求就是一个入口文件example.js依赖于文件a、b、c,其中a和b是和example.js在同一目录文件下的,而c是在node_modules中的, 我们要将这几个模块构建成一个js文件,输入bundle.js。
- bundle.js 的头部信息都是一致的,如都是一个自执行函数的定义,其中有一个核心函数 __webpack_require__ ,最终这个自执行函数返回的是入口文件的模块。 然后依次向下执行。
- 需要分析出各个模块之间的依赖关系,比如这里的example.js是依赖于a、b、c的。
- 并且我们使用require('c')的时候,会自动导入node_modules中的相关文件,那么这一定是有一个详细的查询机制的。
- 在生成的bundle.js文件中,每一个模块都是具有一个唯一的模块id的,引用时我们只需要引用这个id即可。
分析模块依赖关系:
CommonJS不同于AMD,是不会在一开始声明所有依赖的。CommonJS最显著的特征就是用到的时候再require
,所以我们得在整个文件的范围内查找到底有多少个require
。
webpack是使用commonjs的规范来写脚本的,但是对amd、cmd的书写方式也支持的很好。 这里简单区分一下几种模块化的方法。 ADM/CMD是专门为浏览器端的模块化加载来制定的, 通常使用的方式就是define() 的方式,其中amd要求必须在文件的开头声明所有依赖的文件,而cmd则没有这个要求,而是在使用的时候require即可, 即: amd是提前加载的,而cmd是在使用时再加载的,这是两者的区别之一。Commonjs是服务器端node的书写方式,如使用的时候require,而在导出的时候使用module.export,但是如今Commonjs规范已经不仅仅只适用于服务器端了,而是也适用于桌面端,但是随着其使用越来越广泛,名字由之前的severjs改为了common.js。 而es6中的 export 和 import会在babel的编译下编译为浏览器可以执行的方式。
怎么办呢?
最先蹦入脑海的思路是正则。然而,用正则来匹配require
,有以下两个缺点:
- 如果
require
是写在注释中,也会匹配到。 - 如果后期要支持
require
的参数是表达式的情况,如require('a'+'b')
,正则很难处理。
因此,正则行不通。
一种正确的思路是:使用JS代码解析工具(如esprima或者acorn),将JS代码转换成抽象语法树(AST),再对AST进行遍历。这部分的核心代码是parse.js。
在处理好了require
的匹配之后,还有一个问题需要解决。那就是匹配到require
之后需要干什么呢?
举个例子:
// example.js
let a = require('a');
let b = require('b');
let c = require('c');
这里有三个require
,按照CommonJS的规范,在检测到第一个require
的时候,根据require即执行
的原则,程序应该立马去读取解析模块a
。如果模块a
中又require
了其他模块,那么继续解析。也就是说,总体上遵循深度优先遍历算法。这部分的控制逻辑写在buildDeps.js中。
寻找模块:
在完成依赖分析的同时,我们需要解决另外一个问题,那就是如何找到模块?也就是模块的寻址问题。
举个例子:
// example.js
let a = require('a');
let b = require('b');
let c = require('c');
在模块example.js
中,调用模块a、b、c
的方式都是一样的。
但是,实际上他们所在的绝对路径层级并不一致:a和b
跟example
同级,而c
位于与example
同级的node_modules
中。所以,程序需要有一个查找模块的算法,这部分的逻辑在resolve.js中。
目前实现的查找逻辑是:
- 如果给出的是绝对路径/相对路径,只查找一次。找到?返回绝对路径。找不到?返回false。
- 如果给出的是模块的名字,先在入口js(example.js)文件所在目录下寻找同名JS文件(可省略扩展名)。找到?返回绝对路径。找不到?走第3步。
- 在入口js(example.js)同级的node_modules文件夹(如果存在的话)查找。找到?返回绝对路径。找不到?返回false。
当然,此处实现的算法还比较简陋,之后有时间可以再考虑实现逐层往上的查找,就像nodejs默认的模块查找算法那样。
拼接 bundle.js :
这是最后一步了。
在解决了模块依赖
和模块查找
的问题之后,我们将会得到一个依赖关系对象depTree
,此对象完整地描述了以下信息:都有哪些模块,各个模块的内容是什么,他们之间的依赖关系又是如何等等。具体的结构如下
{
"modules": {
"/Users/youngwind/www/fake-webpack/examples/simple/example.js": {
"id": ,
"filename": "/Users/youngwind/www/fake-webpack/examples/simple/example.js",
"name": "/Users/youngwind/www/fake-webpack/examples/simple/example.js",
"requires": [
{
"name": "a",
"nameRange": [
, ],
"id":
},
{
"name": "b",
"nameRange": [
, ],
"id":
},
{
"name": "c",
"nameRange": [
, ],
"id":
}
],
"source": "let a = require('a');\nlet b = require('b');\nlet c = require('c');\na();\nb();\nc();\n"
},
"/Users/youngwind/www/fake-webpack/examples/simple/a.js": {
"id": ,
"filename": "/Users/youngwind/www/fake-webpack/examples/simple/a.js",
"name": "a",
"requires": [],
"source": "// module a\n\nmodule.exports = function () {\n console.log('a')\n};"
},
"/Users/youngwind/www/fake-webpack/examples/simple/b.js": {
"id": ,
"filename": "/Users/youngwind/www/fake-webpack/examples/simple/b.js",
"name": "b",
"requires": [],
"source": "// module b\n\nmodule.exports = function () {\n console.log('b')\n};"
},
"/Users/youngwind/www/fake-webpack/examples/simple/node_modules/c.js": {
"id": ,
"filename": "/Users/youngwind/www/fake-webpack/examples/simple/node_modules/c.js",
"name": "c",
"requires": [],
"source": "module.exports = function () {\n console.log('c')\n}"
}
},
"mapModuleNameToId": {
"/Users/youngwind/www/fake-webpack/examples/simple/example.js": ,
"a": ,
"b": ,
"c":
}
}
打包优化
使用了react全家桶之后,打包出的bundle.js是非常大的, 所以对之进行优化是十分有必要的。
(1)、使用压缩插件,如下:
在webpack.config.js中进行配置下面的代码:
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
这样打包出来的文件可以从5M减少到1.7左右。
(2)、开发过程中使用 webpack-dev-server.
我们当然可以每次使用打包出来的文件,但是更好的做法是将不把文件打包出来,然后从硬盘中获取,而是直接打包到内存中(即webapck-dev-server的作用),这样,我们就可以直接从内存中获取了,好处就是速度很快。 显然内存的读取速度是大于硬盘的。
webpack原理探究 && 打包优化的更多相关文章
- vue+webpack+element-ui项目打包优化速度与app.js、vendor.js打包后文件过大
从开通博客到现在也没写什么东西,最近几天一直在研究vue+webpack+element-ui项目打包速度优化,想把这几天的成果记录下来,可能对前端牛人来说我这技术比较菜,但还是希望给有需要的朋友提供 ...
- Webpack + Vue 多页面项目升级 Webpack 4 以及打包优化
0. 前言 早在 2016 年我就发布过一篇关于在多页面下使用 Webpack + Vue 的配置的文章,当时也是我在做自己一个个人项目时遇到的配置问题,想到别人也可能遇到跟我同样的问题,就把配置的思 ...
- Webpack 打包优化之体积篇
谈及如今欣欣向荣的前端圈,不仅有各类框架百花齐放,如Vue, React, Angular等等,就打包工具而言,发展也是如火如荼,百家争鸣:从早期的王者Browserify, Grunt,到后来赢得宝 ...
- webpack 打包优化的四种方法(多进程打包,多进程压缩,资源 CDN,动态 polyfill)
如今,webpack 毫无疑问是前端构建领域里最耀眼的一颗星,无论你前端走哪条路线,都需要有很强的webpack 知识.webpack 的基本用法这里就不展开讲了.主要探讨一下如何提高 webpack ...
- Webpack 打包优化之速度篇
在前文 Webpack 打包优化之体积篇中,对如何减小 Webpack 打包体积,做了些探讨:当然,那些法子对于打包速度的提升,也是大有裨益.然而,打包速度之于开发体验和及时构建,相当重要:所以有必要 ...
- 记一次webpack打包优化
未进行打包优化的痛点: 随着项目的不断扩大,引入的第三方库会越来越多,我们每次build的时候会对所有的文件进行打包,耗时必定很长,不利于日常开发. 解决思路: 第三方库我们只是引入到项目里来,一般不 ...
- Vue发布过程中遇到坑,以及webpack打包优化
前言 这段时间,本人自己做了一个vue画面部署到自己的服务器上,发现运行速度慢的的惊人,虽然服务器很渣(本人没什么钱,只能租最差的服务器,主要是给自己学习用的),但是这样开发出来的网站简直不能用,所以 ...
- 小型Web页打包优化(下)
之前我们推送了一篇小型Web项目打包优化文章,(链接),我们使用了一段时间, 在这过程中我们也一直在思考, 怎么能把结构做的更好.于是我们改造了一版, 把可以改进的地方和可能会出现的问题, 在这一版中 ...
- webpack原理与实战
webpack是一个js打包工具,不一个完整的前端构建工具.它的流行得益于模块化和单页应用的流行.webpack提供扩展机制,在庞大的社区支持下各种场景基本它都可找到解决方案.本文的目的是教会你用we ...
随机推荐
- 20155335俞昆《java程序设计》第6周总结
20155335 <Java程序设计>第6周学习总结 ## 教材学习内容总结 首先,我们需要了解输入和输出的关系,我想,这不同于c语言中的输入和输出,我们首先明白,Java中以串流 ...
- 使用Intel的FPGA电源设计FPGA 供电的常用反馈电阻阻值
使用Intel的FPGA电源设计FPGA 供电的常用反馈电阻阻值. 当前仅总结使用EN5339芯片的方案 Vout = Ra*0.6/Rb + 0.6 芯片手册推荐Ra取348K,则 3.3V时,取R ...
- [Yii2]yiisoft/yii2 2.0.2 requires bower-asset/jquery 2.1.*@stable | 1.11.*@stable -> no matching package found
composer require "dektrium/yii2-user:0.9.*@dev" 一直安装失败,提示:Your requirements could not be r ...
- 7.使用ZookeeperNet进行CDU操作参数详解
一.创建,删除,更新 1. zkCli.sh 中使用命令执行. 2. 学会查看api文档 zookeeper C# driver 官方文档,文档是java代码的. http://zookeeper.a ...
- VUE 学习笔记 一 指令
1.声明式渲染 v-bind 特性被称为指令.指令带有前缀 v-,以表示它们是 Vue 提供的特殊特性 <div id='app'> <span v-bind:title=" ...
- C#语言各个版本特性(三)
三.查询集合 1.找出List<Product>列表中符合特定条件的所有元素 C#1.1 查询步骤:循环,if判断,打印 product类 using System.Collections ...
- Devexpress Tab Control 文档
https://documentation.devexpress.com/WPF/8078/Controls-and-Libraries/Layout-Management/Tab-Control/P ...
- Android 屏幕适应
基础知识: 屏幕密度: Density-independent pixel (dp):密度无关像素单位(一个相对的值).1dp 的大小相当于一个 160 dpi 屏幕上一个像素的大小. 计算方法:px ...
- ceph 运维常用指令
集群 启动一个ceph 进程 启动mon进程 service ceph start mon.node1 启动msd进程 service ceph start mds.node1 启动osd进程 ser ...
- php实现socket简单的例子
一.Socket简介 1.socket只不过是一种数据结构 2.使用这个socket数据结构去开始一个客户端和服务器之间的会话 3.服务器是一直在监听准备产生一个新的会话.当一个客户端连接服务端,它就 ...