详细解析Webpack是怎么运行的

在平时开发中我们经常会用到 Webpack这个时下最流行的前端打包工具。它打包开发代码,输出能在各种浏览器运行的代码,提升了开发至发布过程的效率。

我们知道一份Webpack配置文件主要包含入口( entry)、输出文件( output)、模式、加载器( Loader)、插件( Plugin)等几个部分。但如果只需要组织 JS 文件的话,指定入口和输出文件路径即可完成一个迷你项目的打包。下面我们来通过一个简单的项目来看一下Webpack是怎样运行的。

同步加载

本文使用 webpack ^4.30.0 作示例.为了更好地观察产出的文件,我们将模式设置为 development 关闭代码压缩,再开启 source-map 支持原始源代码调试。除此之外。我们还简单的写了一个插件 MyPlugin来去除源码中的注释。

新建 src/index.js:

console.log('Hello webpack!');

新建 webpack配置文件 webpack.config.js

const path = require('path');
const MyPlugin = require('./src/MyPlugin.js')
module.exports = {
 mode: 'development',
 devtool: 'source-map',
 entry: './src/index.js',
 output: {
 path: path.resolve(__dirname, 'dist')
 },
 plugins:[
 new MyPlugin()
 ]
};

新建 src/MyPlugin.js。了解webpack插件更多信息

class MyPlugin {
 constructor(options) {
 this.options = options
 this.externalModules = {}
 }
 apply(compiler) {
 var reg = /("([^\\\"]*(\\.)?)*")|('([^\\\']*(\\.)?)*')|(\/{2,}.*?(\r|\n))|(\/\*(\n|.)*?\*\/)|(\/\*\*\*\*\*\*\/)/g
 compiler.hooks.emit.tap('CodeBeautify', (compilation)=> {
  Object.keys(compilation.assets).forEach((data)=> {
  let content = compilation.assets[data].source() // 欲处理的文本
  content = content.replace(reg, function (word) { // 去除注释后的文本
   return /^\/{2,}/.test(word) || /^\/\*!/.test(word) || /^\/\*{3,}\//.test(word) ? "" : word;
  });
  compilation.assets[data] = {
   source(){
   return content
   },
   size(){
   return content.length
   }
  }
  })
 })
 }
}
module.exports = MyPlugin

现在我们运行命令 webpack --config webpack.config.js ,打包完成后会多出一个输出目录 dist:dist/main.js。main 是 webpack 默认设置的输出文件名,我们快速瞄一眼这个文件:

(function(modules){
 // ...
})({
 "./src/index.js": (function(){
 // ...
 })
});

整个文件只含一个立即执行函数(IIFE),我们称它为 webpackBootstrap,它仅接收一个对象 —— 未加载的 模块集合(modules),这个modules 对象的 key 是一个路径,value 是一个函数。你也许会问,这里的模块是什么?它们又是如何加载的呢?

在细看产出代码前,我们先丰富一下源代码:

新文件 src/utils/math.js:

export const plus = (a, b) => {
 return a + b;
};

修改 src/index.js:

import { plus } from './utils/math.js';
console.log('Hello webpack!');
console.log('1 + 2: ', plus(1, 2));

我们按照 ES 规范的模块化语法写了一个简单的模块 src/utils/math.js,给 src/index.js 引用。Webpack 用自己的方式支持了 ES6 Module 规范,前面提到的 module 就是和 ES6 module 对应的概念。

接下来我们看一下这些模块是如何通 ES5 代码实现的。再次运行命令 webpack --config webpack.config.js 后查看输出文件:

(function(modules){
 // ...
})({
 "./src/index.js": (function(){
 // ...
 }),
 "./src/utils/math.js": (function() {
 // ...
 })
});

IIFE 传入的 modules 对象里多了一个键值对,对应着新模块 src/utils/math.js,这和我们在源代码中拆分的模块互相呼应。然而,有了 modules 只是第一步,这份文件最终达到的效果应该是让各个模块按开发者编排的顺序运行。

探究 webpackBootstrap

接下来看看 webpackBootstrap 函数中有些什么:

// webpackBootstrap
(function(modules){
 // 缓存 __webpack_require__ 函数加载过的模块
 var installedModules = {};
 /**
 * Webpack 加载函数,用来加载 webpack 定义的模块
 * @param {String} moduleId 模块 ID,一般为模块的源码路径,如 "./src/index.js"
 * @returns {Object} exports 导出对象
 */
 function __webpack_require__(moduleId) {
 // ...
 }
 // 在 __webpack_require__ 函数对象上挂载一些变量及函数 ...
 // 传入表达式的值为 "./src/index.js"
 return __webpack_require__(__webpack_require__.s = "./src/index.js");
})(/* modules */);

可以看到其实主要做了两件事:

定义一个模块加载函数__webpack_require__。

使用加载函数加载入口模块 "./src/index.js"。

整个 webpackBootstrap 中只出现了入口模块的影子,那其他模块又是如何加载的呢?我们顺着 __webpack_require__("./src/index.js") 细看加载函数的内部逻辑:

function __webpack_require__(moduleId) {
 // 重复加载则利用缓存
 if (installedModules[moduleId]) {
 return installedModules[moduleId].exports;
 }
 // 如果是第一次加载,则初始化模块对象,并缓存
 var module = installedModules[moduleId] = {
 i: moduleId, // 模块 ID
 l: false,  // 模块加载标识
 exports: {} // 模块导出对象
 };
 /**
 * 执行模块
 * @param module.exports -- 模块导出对象引用,改变模块包裹函数内部的 this 指向
 * @param module -- 当前模块对象引用
 * @param module.exports -- 模块导出对象引用
 * @param __webpack_require__ -- 用于在模块中加载其他模块
 */
 modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
 // 模块加载标识置为已加载
 module.l = true;
 // 返回当前模块的导出对象引用
 return module.exports;
}

首先,加载函数使用了闭包变量 installedModules,用来将已加载过的模块保存在内存中。 接着是初始化模块对象,并把它挂载到缓存里。然后是模块的执行过程,加载入口文件时 modules[moduleId] 其实就是./src/index.js对应的模块函数。执行模块函数前传入了跟模块相关的几个实参,让模块可以导出内容,以及加载其他模块的导出。最后标识该模块加载完成,返回模块的导出内容。

根据 __webpack_require__ 的缓存和导出逻辑,我们得知在整个 IIFE 运行过程中,加载已缓存的模块时,都会直接返回installedModules[moduleId].exports,换句话说,相同的模块只有在第一次引用的时候才会执行模块本身。

模块执行函数

__webpack_require__中通过 modules[moduleId].call() 运行了模块执行函数,下面我们就进入到webpackBootstrap 的参数部分,看看模块的执行函数。

/*** 入口模块 ./src/index.js ***/
"./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {
 "use strict";
// 用于区分 ES 模块和其他模块规范,不影响理解 demo,战略跳过。
 __webpack_require__.r(__webpack_exports__);
 /* harmony import */
 // 源模块代码中,`import {plus} from './utils/math.js';` 语句被 loader 解析转化。
 // 加载 "./src/utils/math.js" 模块,
 var _utils_math_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/utils/math.js");
 console.log('Hello webpack!');
 console.log('1 + 2: ', Object(_utils_math_js__WEBPACK_IMPORTED_MODULE_0__["plus"])(1, 2));
}),
"./src/utils/math.js": (function (module, __webpack_exports__, __webpack_require__) {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */
// 源模块代码中,`export` 语句被 loader 解析转化。
 __webpack_require__.d(__webpack_exports__, "plus", function () {
 return plus;
 });
 const plus = (a, b) => {
 return a + b;
 };
})

执行顺序是:入口模块 -> 工具模块 -> 入口模块。入口模块中首先就通过 __webpack_require__("./src/utils/math.js") 拿到了工具模块的 exports 对象。再看工具模块,ES 导出语法转化成了__webpack_require__.d(__webpack_exports__, [key], [getter]),而 __webpack_require__.d 函数的定义在 webpackBootstrap 内:

// 定义 exports 对象导出的属性。
 __webpack_require__.d = function (exports, name, getter) {
 // 如果 exports (不含原型链上)没有 [name] 属性,定义该属性的 getter。
 if (!__webpack_require__.o(exports, name)) {
  Object.defineProperty(exports, name, {
  enumerable: true,
  get: getter
  });
 }
 };
 // 包装 Object.prototype.hasOwnProperty 函数。
 __webpack_require__.o = function (object, property) {
 return Object.prototype.hasOwnProperty.call(object, property);
 };

可见 __webpack_require__.d 其实就是 Object.defineProperty 的简单包装.

引用工具模块导出的变量后,入口模块再执行它剩余的部分。至此,Webpack 基本的模块执行过程就结束了。

好了,我们用流程图总结一下Webpack 模块的加载思路:

异步加载

有上面的打包我们发现将不同的打包进一个 main.js 文件。main.js 会集中消耗太多网络资源,导致用户需要等待很久才可以开始与网页交互。

一般的解决方式是:根据需求降低首次加载文件的体积,在需要时(如切换前端路由器,交互事件回调)异步加载其他文件并使用其中的模块。

Webpack 推荐用 ES import() 规范来异步加载模块,我们根据 ES 规范修改一下入口模块的 import 方式,让其能够异步加载模块:

src/index.js

console.log('Hello webpack!');
window.setTimeout(() => {
 import('./utils/math').then(mathUtil => {
 console.log('1 + 2: ' + mathUtil.plus(1, 2));
 });
}, 2000);

工具模块(src/utils/math.js)依然不变,在webpack 配置里,我们指定一下资源文件的公共资源路径(publicPath),后面的探索过程中会遇到。

const path = require('path');
const MyPlugin = require('./src/MyPlugin.js')
module.exports = {
 mode: 'development',
 devtool: 'source-map',
 entry: './src/index.js',
 output: {
 path: path.resolve(__dirname, 'dist'),
 publicPath: '/dist/'
 },
 plugins:[
 new MyPlugin()
 ]
};

接着执行一下打包,可以看到除了 dist/main.js 外,又多了一个 dist/0.js ./src/utils/math.js。模块从main chunk 迁移到了 0 chunk 中。而与 demo1 不同的是,main chunk 中添加了一些用于异步加载的代码,我们概览一下:

// webpackBootstrap
(function (modules) {
 // 加载其他 chunk 后的回调函数
 function webpackJsonpCallback(data) {
 // ...
 }
 // ...
 // 用于缓存 chunk 的加载状态,0 为已加载
 var installedChunks = {
 "main": 0
 };
 // 拼接 chunk 的请求地址
 function jsonpScriptSrc(chunkId) {
 // ...
 }
 // 同步 require 函数,内容不变
 function __webpack_require__(moduleId) {
 // ...
 }
 // 异步加载 chunk,返回封装加载过程的 promise
 __webpack_require__.e = function requireEnsure(chunkId) {
 // ...
 }
 // ...
 // defineProperty 的包装,内容不变
 __webpack_require__.d = function (exports, name, getter) {}
 // ...
 // 根据配置文件确定的 publicPath
 __webpack_require__.p = "/dist/";
 /**** JSONP 初始化 ****/
 var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
 var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
 jsonpArray.push = webpackJsonpCallback;
 jsonpArray = jsonpArray.slice();
 for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
 var parentJsonpFunction = oldJsonpFunction;
 /**** JSONP 初始化 ****/
 return __webpack_require__(__webpack_require__.s = "./src/index.js");
})({
 "./src/index.js": (function(module, exports, __webpack_require__) {
 document.write('Hello webpack!\n');
 window.setTimeout(() => {
  __webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ./utils/math */ "./src/utils/math.js")).then(mathUtil => {
  console.log('1 + 2: ' + mathUtil.plus(1, 2));
  });
 }, 2000);
 })
})

可以看到 webpackBootstrap 的函数体部分增加了一些内容,参数部分移除了"./src/utils/math.js"模块。跟着包裹函数的执行顺序,我们先聚焦到「JSONP 初始化」部分:

// 存储 jsonp 的数组,首次运行为 []
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
// 保存 jsonpArray 的 push 函数,首次运行为 Array.prototype.push
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
// 将 jsonpArray 的 push 重写为 webpackJsonpCallback (加载其他 chunk 后的回调函数)
jsonpArray.push = webpackJsonpCallback;
// 将 jsonpArray 重置为正常数组,push 重置为 Array.prototype.push
jsonpArray = jsonpArray.slice();
// 由于 jsonpArray 为 [],不做任何事
for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
// Array.prototype.push
var parentJsonpFunction = oldJsonpFunction;

初始化结束后,变化就是 window 上挂载了一个 webpackJsonp 数组,它的值为[];此外,这个数组的 push 被改写为 webpackJsonpCallback 函数,我们在后面会提到这些准备工作的作用。

接着是 __webpack_require__入口模块,由于 __webpack_require__ 函数没有改变,我们继续观察入口模块执行函数有了什么变化。

显然,import('../utils/math.js') 被转化为__webpack_require__.e(0).then(__webpack_require__.bind(null, "./src/utils/math.js"))。0 是 ./src/utils/math.js 所在 chunk 的id,「同步加载模块」的逻辑拆分成了「先加载 chunk,完成后再加载模块」。

我们翻到 __webpack_require__.e的定义位置:

__webpack_require__.e = function requireEnsure(chunkId) {
 var promises = [];
 // installedChunks 是在 webpackBootstrap 中维护的 chunk 缓存
 var installedChunkData = installedChunks[chunkId];
 // chunk 未加载
 if(installedChunkData !== 0) {
 // installedChunkData 为 promise 表示 chunk 加载中
 if(installedChunkData) {
  promises.push(installedChunkData[2]);
 } else {
  /*** 首次加载 chunk: ***/
  // 初始化 promise 对象
  var promise = new Promise(function(resolve, reject) {
  installedChunkData = installedChunks[chunkId] = [resolve, reject];
  });
  promises.push(installedChunkData[2] = promise);
  // 创建 script 标签加载 chunk
  var head = document.getElementsByTagName('head')[0];
  var script = document.createElement('script');
  var onScriptComplete;
  // ... 省略一些 script 属性设置
  // src 根据 publicPath 和 chunkId 拼接
  script.src = jsonpScriptSrc(chunkId);
  // 加载结束回调函数,处理 script 加载完成、加载超时、加载失败的情况
  onScriptComplete = function (event) {
  script.onerror = script.onload = null; // 避免 IE 内存泄漏问题
  clearTimeout(timeout);
  var chunk = installedChunks[chunkId];
  // 处理 script 加载完成,但 chunk 没有加载完成的情况
  if(chunk !== 0) {
   // chunk 加载中
   if(chunk) {
   var errorType = event && (event.type === 'load' ? 'missing' : event.type);
   var realSrc = event && event.target && event.target.src;
   var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')');
   error.type = errorType;
   error.request = realSrc;
   // reject(error)
   chunk[1](error);
   }
   // 统一将没有加载的 chunk 标记为未加载
   installedChunks[chunkId] = undefined;
  }
  };
  // 设置 12 秒超时时间
  var timeout = setTimeout(function(){
  onScriptComplete({ type: 'timeout', target: script });
  }, 120000);
  script.onerror = script.onload = onScriptComplete;
  head.appendChild(script);
  /*** 首次加载 chunk ***/
 }
 }
 return Promise.all(promises);
};

看起来有点长,我们一步步剖析,先从第一行和最后一行来看,整个函数将异步加载的过程封装到了 promise 中,最终导出。

接着从第二行开始,installedChunkData 从缓存中取值,显然首次加载 chunk 时此处是 undefined。接下来,installedChunkData 的 undefined 值触发了第一层 if 语句的判断条件。紧接着进行到第二层 if 语句,此时根据判断条件走入 else块,这里 if 块里的内容我们先战略跳过,else里主要有两块内容,一是 chunk 脚本加载过程,这个过程创建了一个 script 标签,使其请求 chunk所在地址并执行chunk 内容;二是初始化 promise ,并用 promis 控制 chunk 文件加载过程。

不过,我们只在这段 else 代码块中找到了 reject 的使用处,也就是在 chunk 加载异常时chunk[1](error) 的地方,但并没发现更重要的 resolve的使用地点,仅仅是把 resolve 挂在了缓存上(installedChunks[chunkId] = [resolve, reject])。

这里的 chunk 文件加载下来会发生什么呢?让我们打开dist/0.js一探究竟:

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0], {
 "./src/utils/math.js":
 (function (module, __webpack_exports__, __webpack_require__) {
  "use strict";
  __webpack_require__.r(__webpack_exports__);
  /* harmony export (binding) */
  __webpack_require__.d(__webpack_exports__, "plus", function () {
  return plus;
  });
  const plus = (a, b) => {
  return a + b;
  };
 })
}]);

我们发现了:

久违的 ./src/utils/math.js 模块

window["webpackJsonp"] 数组的使用地点

这段代码开始执行,把异步加载相关的 chunk id 与模块传给push 函数。而前面已经提到过,window["webpackJsonp"]数组的 push 函数已被重写为 webpackJsonpCallback 函数,它的定义位置在 webpackBootstrap 中:

function webpackJsonpCallback(data) {
 var chunkIds = data[0];
 var moreModules = data[1];
 // then flag all "chunkIds" as loaded and fire callback
 var moduleId, chunkId, i = 0, resolves = [];
 // 将 chunk 标记为已加载
 for(;i < chunkIds.length; i++) {
 chunkId = chunkIds[i];
 if(installedChunks[chunkId]) {
  resolves.push(installedChunks[chunkId][0]);
 }
 installedChunks[chunkId] = 0;
 }
 // 把 "moreModules" 加到 webpackBootstrap 中的 modules 闭包变量中。
 for(moduleId in moreModules) {
 if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
  modules[moduleId] = moreModules[moduleId];
 }
 }
 // parentJsonpFunction 是 window["webpackJsonp"] 的原生 push
 // 将 data 加入全局数组,缓存 chunk 内容
 if(parentJsonpFunction) parentJsonpFunction(data);
 // 执行 resolve 后,加载 chunk 的 promise 状态变为 resolved,then 内的函数开始执行。
 while(resolves.length) {
 resolves.shift()();
 }
};

走进这个函数中,意味着异步加载的 chunk 内容已经拿到,这个时候我们要完成两件事,一是让依赖这次异步加载结果的模块继续执行,二是缓存加载结果。

关于第一点,我们回忆一下之前 __webpack_require__.e 的内容,此时 chunk 还处于「加载中」的状态,也就是说对应的 installedChunks[chunkId] 的值此时为[resolve, reject, promise]。 而这里,chunk 已经加载,但 promise 还未决议,于是 webpackJsonpCallback 内部定义了一个 resolves 变量用来收集 installedChunks 上的 resolve 并执行它。`

接下来说到第二点,就要涉及几个层面的缓存了。

首先是 chunk 层面,这里有两个相关操作,操作一将 installedChunks[chunkId] 置为 0 可以让 __webpack_require__.e在第二次加载同一 chunk 时返回一个立即决议的 promise(Promise.all([]));操作二将 chunk data 添加进 window["webpackJsonp"] 数组,可以在多入口模式时,方便地拿到已加载过的 chunk 缓存。通过以下代码实现:

/*** 缓存执行部分 ***/
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
// ...
for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
var parentJsonpFunction = oldJsonpFunction;
/*** 缓存执行部分 ***/

/*** 缓存添加部分 ***/
function webpackJsonpCallback(data) {
 //...
 // 此处的 parentJsonpFunction 是 window["webpackJsonp"] 数组的原生 push
 if (parentJsonpFunction) parentJsonpFunction(data);
 //...
}
/*** 缓存添加部分 ***/

而在 modules 层面,chunk 中的 moreModules 被合入入口文件的modules 中,可供下一个微任务中的 __webpack_require__ 同步加载模块。

({
 "./src/index.js":
 (function (module, exports, __webpack_require__) {
  console.log('Hello webpack!');
  window.setTimeout(() => {
  __webpack_require__.e(0).then(__webpack_require__.bind(null, "./src/utils/math.js")).then(mathUtil => {
   console.log('1 + 2: ' + mathUtil.plus(1, 2));
  });
  }, 2000);
 })
});

__webpack_require__.e(0)返回的 promise 决议后,__webpack_require__.bind(null, "./src/utils/math.js") 可以加载到chunk携带的模块,并返回模块作为下一个微任务函数的入参,接下来就是 Webpack Loader 翻译过的其他业务代码了。

现在让我们把异步流程梳理一下:

更多Webpack的基础知识请点击下面的相关文章

(0)

相关推荐

  • 详解基于webpack搭建react运行环境

    最近由于项目需要,所以尝试使用webpack搭建react运行环境,在克服了一系列困难后,终于能在页面输出"Hello World",特地把整个过程记录下来,利人利己. 首先我创建了一个文件夹webpack-helloworld-demo,之后的大部分操作都会在这个文件夹下进行. 1.初始化文件夹 npm init //初始化一个package.json文件 git init //产生.git文件,便于之后版本提交回退 2.搭建webpack环境 npm install webpack

  • 详解webpack运行Babel教程

    摘要:Babel是转码器,webpack是打包工具,它们应该如何一起使用呢? GitHub仓库: Fundebug/webpack-babel-tutorial ES6 + IE10 = 语法错误! test.js使用了ES6的 箭头函数 : setTimeout(() => { console.log("Hello, Fundebug!"); }, 100) 由于低版本的浏览器没有支持ES6语法,这就意味着代码会出错.例如,在IE 10浏览器中,会出现"语法错误&qu

  • 详解webpack打包vue项目之后生成的dist文件该怎么启动运行

    亲测,webpack打包vue项目之后生成的dist文件可以部署到 express 服务器上运行. 我的vue项目结构如下: 1. 进入该vue项目目录,打开git bash,执行:npm run build(在package.json的scripts配置) 执行成功如下图所示: 然后此时你会发现项目下多了一个 dist 文件夹,dist下文件便是项目打包之后生成的文件. 此时我们直接在浏览器中打开index.html,是会报错的,什么都看不到. 2. 安装express-generator生成

  • webpack4与babel配合使es6代码可运行于低版本浏览器的方法

    使用es6+新语法编写代码,可是不能运行于低版本浏览器,需要将语法转换成es5的.那就借助babel转换,再加上webpack打包,实现代码的转换. 转换包括两部分:语法和API let.const这些是新语法. new promise()等这些是新API. 简单代码 类库 utils.js const name = 'weiqinl' let year = new Date().getFullYear() export { name, year } index.js import _ from

  • 浅谈TypeScript 用 Webpack/ts-node 运行的配置记录

    公司项目代码是用 TypeScript 写的, 中间遇到有些代码不要放到 Node 里面去跑. 具体场景一些路由配置, 比较大的一块 JSON 数据定义在 TypeScript 里. 我另外有增加脚本, 基于这些 JSON 数据用来生成切换路由的函数. 这就需要运行 TypeScript 了, 而且可能包含一些额外的业务代码. 首先 Node 运行 TypeScript 有提供 ts-node 用来处理. ts-node 会先编译 TypeScript 代码到 JavaScript, 再调用 N

  • 浅谈在vue中用webpack打包之后运行文件的问题以及相关配置方法

    1.vue中的vue-cli打包 最近在用vue写一个小项目,其中就用到了vue脚手架工具vue-cli,在测试打包后能否运行过程中遇到不少问题,而且在网上这些问题答案都不太好找,废话不多说,进入正题. a.执行打包命令:npm run build b.打包之后生成的文件夹为根目录下的dist文件: c.进入dist中 在运行这个index.html之前先说说一些打包配置问题: 在config文件夹下可以看到一个index.js文件,打开其中可以看到有几个属性分别是: assetsRoot: p

  • 使用webpack打包后的vue项目如何正确运行(express)

    我们知道使用webpack打包vue项目后会生成一个dist文件夹,dist文件夹下有html文件和其他css.js以及图片等,那么打包后的文件该如何正确运行呢? 倘若直接打开html文件,会报如下错误: 那么该如何运行呢?其实可以将生成的dist文件部署到express服务器上运行. (1).安装express-generator生成器. npm install express-generator -g // 也可使用cnpm比较快 (2).创建一个express项目. express exp

  • 详细解析Webpack是怎么运行的

    在平时开发中我们经常会用到 Webpack这个时下最流行的前端打包工具.它打包开发代码,输出能在各种浏览器运行的代码,提升了开发至发布过程的效率. 我们知道一份Webpack配置文件主要包含入口( entry).输出文件( output).模式.加载器( Loader).插件( Plugin)等几个部分.但如果只需要组织 JS 文件的话,指定入口和输出文件路径即可完成一个迷你项目的打包.下面我们来通过一个简单的项目来看一下Webpack是怎样运行的. 同步加载 本文使用 webpack ^4.3

  • Mysql中复制详细解析

    1.mysql复制概念 指将主数据库的DDL和DML操作通过二进制日志传到复制服务器上,然后在复制服务器上将这些日志文件重新执行,从而使复制服务器和主服务器的数据保持同步.复制过程中一个服务器充当主服务器(master),而一个或多个其它服务器充当从服务器(slaves).主服务器将更新重新写入二进制日志文件,并维护文件的一个索引以跟踪日志循环.这些日志可以记录发送到从服务器的更新.当一个从服务器连接主服务器时,它通知主服务器.从服务器在日志中读取的最后一次成功更新的位置.从服务器接受从那时起发

  • Tomcat中的catalina.bat原理详细解析

    前言 本文主要给大家详细解析了关于Tomcat中catalina.bat原理的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. tomcat 的真正启动是在 catalina.bat 设置并启动的.startup.bat 只是找到catalina.bat 然后执行catalina.bat 来启动tomat的.下面我们来分析下catalina.bat 验证CATALINA_HOME 环境变量 验证CATALINA_HOME 设置是否正确,如果不正确,重新设置CATALIN

  • 对MySQL配置参数 my.ini/my.cnf的详细解析

    以下的文章主要描述的是对MySQL配置参数 my.ini/my.cnf的详细解析,我们主要是以实例演示的方式来对MySQL配置参数 my.ini/my.cnf的实际操作步骤进行说明,以下就是相关内容的具体描述. 1.获取当前配置参数 要优化MySQL配置参数,首先要了解当前的配置参数以及运行情况.使用下列命令可以获得目前服务器使用的配置参数: 复制代码 代码如下: mysqld –verbose –help mysqladmin variables extended-status –u root

  • Mybatis中的config.xml配置文件详细解析

    经过前面的文章,我觉得对Mybatis的正题理解已经足够了,但是对Mybatis的使用,我觉得还是会有一点的模糊,就我个人而言,我觉得掌握好Mybatis框架,主要要明白三个文件,第一个就是等下要谈论的Mybatis-comfig.xml文件,还有就是**Mapper.xml,以及我们所定义的Mapper类,理解了这三个东西,然后有sql的基础,还有java的基础的话,后面不论是使用基于xml的方法,还是基于java-based Configuration的方法,都会简单的多. 废话不多说,现在

  • Es6 Generator函数详细解析

    ECMAScript 6 (简称 ES6 )作为下一代 JavaScript 语言,将 JavaScript 异步编程带入了一个全新的阶段. Generator函数跟普通函数的写法有非常大的区别: 一是,function关键字与函数名之间有一个星号: 二是,函数体内部使用yield语句,定义不同的内部状态(yield在英语里的意思就是"产出"). 本文重点给大家介绍Es6 Generator函数,具体内容如下所示: /* 一.generator函数的定义 1.Generator 函数是

  • python 日志 logging模块详细解析

    Python 中的 logging 模块可以让你跟踪代码运行时的事件,当程序崩溃时可以查看日志并且发现是什么引发了错误.Log 信息有内置的层级--调试(debugging).信息(informational).警告(warnings).错误(error)和严重错误(critical).你也可以在 logging 中包含 traceback 信息.不管是小项目还是大项目,都推荐在 Python 程序中使用 logging.本文给大家介绍python 日志 logging模块 介绍. 1 基本使用

  • linux下修改文件权限chmod命令详细解析

    使用 Linux 的chmod命令控制谁可以访问读写或运行目标文件. 在 Linux 中,谁可以对文件或目录做什么是通过一系列权限来控制的. 权限可以控制对文件或目录执行的操作(读写或执行). 我们可以使用-l(长格式)选项来ls列出文件和目录的文件权限. ls -l 在每一行中,第一个字符标识列出条目类型.如果它是破折号 ( -),则它是一个文件.如果是字母d ,则是目录. 接下来的九个字符代表三组权限的设置. 前三个字符显示拥有文件的用户的权限(用户权限). 中间三个字符显示用户组成员的权限

  • c语言 数据存储与原码 反码 补码详细解析

    目录 前言 1.数据的类型介绍 1.1整形家族 2.整形在数据内存中的存储 2.1 原码 反码和补码(三种整型数的表示方法) 2.2大小端字节序序的介绍 2.3 练习 3.浮点型在内存中的存储 3.1 先举一个例子 3.2 浮点数储存的规则 前言 学习本章你会了解: 1.数据类型详细介绍 2.整形在内存中的存储:以及了解原码.补码.反码 3.大小端字节序的介绍和判断 4.浮点型在内存中的存储解析 1.数据的类型介绍 在学习数据储存之前,让我们先认识一下数据类型.以下这些数据类型是我们初学c语言时

  • C语言 详细解析时间复杂度与空间复杂度

    目录 一.概念 1.1.算法效率 1.2.时间复杂度 1.3.空间复杂度 二.计算 2.1.大O的渐进表示法 2.2.时间复杂度计算 2.3.空间复杂度计算 三.有复杂度要求的习题 一.概念 1.1.算法效率 如何衡量一个算法的好坏?比如对于以下斐波那契数列: long long Fib(int N) { if (N < 3) return 1; return Fib(N - 1) + Fib(N - 2); } 斐波那契数列用递归实现方式非常简洁,但简洁一定好吗?那该如何衡量其好与坏呢?在学完

随机推荐