webpack

webpack 执行过程

start

npm install webpack webpack-cli -D 

notes

  • npx 可以直接运行node_modules/.bin 目录下的命令
  • 使用loader
// 1 直接用 
require("expose-loader?$!jquery"); 

// 2 配置module
use: 'file-loader' 
use: [
    { loader: 'style-loader' },
    {
      loader: 'css-loader',
      options: {
         modules: true
      }
     },
     { loader: 'sass-loader' }
  ]
  • file-loader 解析 import和require文件为一个路径 ,并把文件emit到输出目录
{
        test: /\.(png|jpe?g|gif)$/i,
        loader: 'file-loader',
        options: {
          outputPath: 'images',
        },
      },
  • url-loader which transforms files into base64 URIs.

jquery 引入方式

  • script 标签
  • entry 加一个vendornew webpack.ProvidePlugin({ $ : 'jquery' })
  • dll-plugin
  • expose-loader 只是把变量暴露到全局,打包不会代码分割
require("expose-loader?$!jquery");

module: {
  rules: [{
          test: require.resolve('jquery'),
          use: [{
              loader: 'expose-loader',
              options: 'jQuery'
          },{
              loader: 'expose-loader',
              options: '$'
          }]
      }]
}

sorcemap

webpack在开发模式时,可以加
devtool: 'inline-source-map'
开启这个配置,快速定位代码出错文件和位置

代码自动编译

3种可以自动编译源代码的实现

  • webpack’s Watch Mode,这种需要手动刷新页面
  • webpack-dev-server,这种可以自动刷新页面
  • webpack-dev-middleware

代码分割

代码分割可以吧代码分成不同的bundle,来实现按需加载和并行加载,实现方式有3种,

  • 多入口(多入口会造成代码库重用,比如都用了jQuery,那么jq都会被打包在入口文件里)
  • SplitChunksPlugin
// webpack 配置 
const path = require("path");
module.exports = {
	mode: 'development',
	entry:{
		index: './src/index',
		main: './src/main',
	},
	output:{
		path: path.join(__dirname, 'dist'),
		filename: '[name].bundle.js'
	},
	optimization: {
     splitChunks: {
       chunks: 'all'
     }
  },
	module:{

	},
}
	// 这样就会把jquery打包成一个单独的chunk
	// mini-css-extract-plugin 和 bundler-loader也是实现这种效果的 
  • 动态加载 dymamic import
  • CommonsChunkPlugin在4版本已经移除了,要使用SplitChunksPlugin配置,如下

devserver

devserver 不输出任意编译文件,而是就像位置在服务器根目录的真实文件,但是在内存中 ,内置了webpack-dev-middle中间件,可以独立使用,也可以放到express当做中间件

// 打包的文件放在内存里 
devServer: {
   contentBase: './dist', 静态资源根目录 
   host: 'localhost',
   port: 8888,
   compress: true,
   open: true
}

url-loader

可以把文件变成base64,options的limit是小于多大会被搞成base64,fallback是大于这个大小是谁处理,默认是file–loader

file-loader

默认会把文件,处理成带hash的名字,根据自身的内容,可以在options里配置name选项

options: {
   outputPath: 'images',
	 name: ''
}

提取css

如果样式都放在style标签,html文件过大加载慢 ,使用mini-css-webpack-plugin替代extract-text-webpack-plugin,好处是可以异步记载和防止重复编译

optimization

optimization : {
  // 和 runtimeChunk : 'single' 效果一样 
	// 创建一个单独的chunk 共享给所有的生成的chunk
	runtimeChunk: {
      name: 'runtime'
  }

}

resolve

resolve : {
	alias: {},
	modules: []//配置除了在node_modules里,还在那里找包
 	mainFields: ['main', 'lib'] // 默认会找package.json 里main的字段
	
}

多页面

可以在htmlWebpackPlugin 里,配置多个模板。然后加载entry对应的chunk的名字

dll

  • dll 引用其实最后是 key是 "./node_modules/_react@16.13.0@react/index.js":
  • value 是 webpack_require(libraryName)(id) 在manifest里有一个对象 有这个id和"./node_modules/_react@16.13.0@react/index.js": ```这个路径
// webpack.dll.js 
module.exports = {
  entry : {
    vendor: ['react', 'react-dom', 'axios']
  },
  output:{
    path: path.join(__dirname, 'dist'),
    filename: '[name].dll.js',
    library: '[name]_library',
    // 默认是var,还有commonjs commonjs2
    libraryTarget: "var"

  },
  plugins:[
    new webpack.DllPlugin({
      path: path.join(__dirname, 'dist', '[name]-manifest.json'),
      name: '[name]_library',
      context: __dirname
    })
  ]
}

// webpack.config.js 
 plugins:[
    new webpack.DllReferencePlugin({
      context: __dirname,
      manifest: require('./dist/vendor-manifest.json')
    }),
  ]

把一个模块单独打包

// webpack.dll.js 
const webpack = require("webpack")
const path = require("path");

module.exports = {
    entry : "./src/lib.js", 
    mode: 'production',
    output:{
      path: path.join(__dirname, 'dist'),
      filename: 'lib.dll.js',
      library: 'hello',
      libraryTarget: "var"
    },
}

plugin happypack
module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        loader: 'happypack/loader?id=babel'
      }
    ]
  },
  plugins: [
    new HappyPack({
        id: 'babel',
        loader: 'babel-loader'
    }),
    new webpack.DllReferencePlugin({
      context: __dirname,
      manifest: require('./dist/vendor-manifest.json')
    }),
    
  ]
ParallelUglifyPlugin 已废弃 使用terser-webpack-plugin

区分不同环境

webpack 内置了区分,只要在optimization 中加nodeEnv, 在开发中就可以直接使用
process.env.NODE_ENV

// 设置不同环境
// 1. package.json 里 
const conf = {
        "build" : "cross-env NODE_ENV=production && webpack",
		"start" : "cross-env NODE_ENV=development && webpack"
}

// 2 抽离webpack.dev.js 和 webpack.production.js 出一个webpack.base.js

// 3. webpack.config.js
const base = require("./webpack.base");
const merge = require("webpack-merge");

const env = process.env.NODE_ENV;
let config;
if(env === 'development') {
  config = require("./webpack.dev")
}else {
  config = require("./webpack.prod")
}

module.exports = merge(base, config)

process

在开发中process,最后是module.exports ,引用的是 process 库

tree shaking

{
  "presets": [
    [
      "@babel/env",
      {
        "targets": {
          "chrome": "60",
          "ie" : 9
        },
        "corejs": {
          "version": 3
        },
        "useBuiltIns": "usage",
        // 不编译为 es5 保持es6 模块化 
        "modules": false 
      }
    ],
  ]
}


eslint

  {
    test: /\.js/,
    loader: 'eslint-loader',
    enforce: 'pre'
  }
	

打包优化

  • parallel-webpack 多页面打包
  • cache-loader 缓存
  • 生产环境,Thread-loader 打包
  • 如编译js,加include 和 exclude
  • 开发环境把optimization 关闭
optimization: {
  removeAvailableModules: false,
  removeEmptyChunks: false,
  splitChunks: false,
	moduleIds: hashed // 可以让一些库 ( 代码不变)的hash值不变
	
	// @depracated uglifyjs 优化配置
	minimizer: [
      new UglifyJsPlugin({
        parallel: true,
        cache: true,
        uglifyOptions: {
          compress: {
            warnings: false,
            ie8: false,
            drop_debugger: true,
            drop_console: true
          },
          output: {
            comments: false,
          },
        }
      })
    ],
	// terser 配置 
	
	const optimization = {
  minimize: true,
  minimizer: [
    new TerserPlugin({
      cache: true,
      parallel: true,
      terserOptions: {
        warnings: false,
        ie8: false,
        safari10: false,
      },
      output: {
        comments: false,
      }
    }),
  ],
}
		
}

  • runtimechunk是运行时的chunk

简单源码

// chunk 0 
const module = {
  "./src/app3/video.js": (function (module, __webpack_exports__, __webpack_require__) {
    eval("__webpack_require__.r(__webpack_exports__); __webpack_exports__['default'] = (function () { console.log('this is video')});");
  })
}

window["webpackJsonp"].push([[0], module])

let modules = {
  "./src/app3/index.js": function (module, exports, __webpack_require__) {
    eval("document.getElementById('click').onclick = function () {" +
      "__webpack_require__.e(0).then(__webpack_require__.bind(null, './src/app3/video.js')).then(r =>{   console.log(r.default());  })" +
      "}");
  }
};


webpack(modules);

function webpack(modules) {

  // jsonp 回调函数
  function webpackJsonpCallback(data) {
    let chunkIds = data[0], moreModules = data[1], resolves = [];

    chunkIds.forEach(id => {
      if (chunksObj[id]) {
        // chunksObj[id][0] 就是promise的resolve方法
        resolves.push(chunksObj[id][0]);
      }
      chunksObj[id] = 0;
    });

    // add "moreModules" to the modules object,
    Object.assign(modules, moreModules)

    if (parentJsonpFunction) {
      console.log(parentJsonpFunction);
      console.log("yes")
      parentJsonpFunction(data);
    }

    while (resolves.length) {
      resolves.shift()();
    }

  };


  // The module cache
  let installedModules = {};

  // object to store loaded and loading chunks
  // undefined = chunk not loaded, null = chunk preloaded/prefetched
  // Promise = chunk loading, 0 = chunk loaded
  let chunksObj = {
    "main": 0
  };

  // 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)
    let 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;
  }

  // This file contains only the entry chunk.
  // The chunk loading function for additional chunks
  __webpack_require__.e = function requireEnsure(chunkId) {
    let promises = [];
    // JSONP chunk loading for javascript
    let installedChunkData = chunksObj[chunkId];
    if (installedChunkData !== 0) { // 0 means "already installed".
      // a Promise means "currently loading".
      if (installedChunkData) {
        promises.push(installedChunkData[2]);
      } else {
        // setup Promise in chunk cache
        let promise = new Promise(function (resolve, reject) {

          installedChunkData = [resolve, reject];
          chunksObj[chunkId] = installedChunkData;
        });

        promises.push(installedChunkData[2] = promise);

        // start chunk loading
        let script = document.createElement('script');

        script.charset = 'utf-8';
        script.timeout = 120;
        script.src = __webpack_require__.p + "chunk-" + ({}[chunkId] || chunkId) + ".js"

        // create error before stack unwound to get useful stacktrace later
        let error = new Error();

        function onScriptComplete(event) {
          // avoid mem leaks in IE.
          script.onerror = script.onload = null;
          clearTimeout(timeout);
          let chunk = chunksObj[chunkId];
          if (chunk !== 0) {
            if (chunk) {
              let errorType = event && (event.type === 'load' ? 'missing' : event.type);
              let realSrc = event && event.target && event.target.src;
              error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
              error.name = 'ChunkLoadError';
              error.type = errorType;
              error.request = realSrc;
              chunk[1](error);
            }
            chunksObj[chunkId] = undefined;
          }
        }

        // 超时之后,也是加载完成
        let timeout = setTimeout(function () {
          onScriptComplete({type: 'timeout', target: script});
        }, 120000);

        script.onerror = script.onload = onScriptComplete;
        document.head.appendChild(script);
      }
    }
    return Promise.all(promises);
  };

  // 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, {enumerable: true, get: getter});
    }
  };

  // define __esModule on exports
  __webpack_require__.r = function (exports) {
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
      Object.defineProperty(exports, Symbol.toStringTag, {value: 'Module'});
    }
    Object.defineProperty(exports, '__esModule', {value: true});
  };

  // create a fake namespace object
  // mode & 1: value is a module id, require it
  // mode & 2: merge all properties of value into the ns
  // mode & 4: return value when already ns object
  // mode & 8|1: behave like require
  __webpack_require__.t = function (value, mode) {
    if (mode & 1) value = __webpack_require__(value);
    if (mode & 8) return value;
    if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
    let ns = Object.create(null);
    __webpack_require__.r(ns);
    Object.defineProperty(ns, 'default', {enumerable: true, value: value});
    if (mode & 2 && typeof value != 'string') for (let key in value) __webpack_require__.d(ns, key, function (key) {
      return value[key];
    }.bind(null, key));
    return ns;
  };

  // getDefaultExport function for compatibility with non-harmony modules
  __webpack_require__.n = function (module) {
    let 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 = "";

  // on error function for async loading
  __webpack_require__.oe = function (err) {
    console.error(err);
    throw err;
  };

  let jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
  let oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
  jsonpArray.push = webpackJsonpCallback;
  jsonpArray = jsonpArray.slice();
  jsonpArray.forEach(e => webpackJsonpCallback(e))
  let parentJsonpFunction = oldJsonpFunction;
  // 加载入口文件
  return __webpack_require__(__webpack_require__.s = "./src/app3/index.js");
}

AST 抽象语法树

  • 语法检查,风格检查,错误检查,代码混淆压缩,代码转换
  • 工具是 javascript parser实现 ,常见的有 esprima、traceur(google)、acorn、shift ,
  • webpack用的是acorn ,babel是 @babel/parser
  • 每个浏览器都是自己的抽象语法树标准,如谷歌的v8,火狐的spiderMonkey是业界标准,
let esprima = require("esprima");
// 遍历语法树
let estraverse = require("estraverse");

let code = "function d(){}";

let tree = esprima.parse(code);

estraverse.traverse(tree, {
  enter(node){
    console.log("in--", node.type)
  },
  leave(node){
    console.log(node.type)
  },
})
// 结果如下,进一次 出一次 
enter-- Program
enter-- FunctionDeclaration
enter-- Identifier
leave-- Identifier
enter-- BlockStatement
leave-- BlockStatement
leave-- FunctionDeclaration
leave-- Program

引入大工具库的优化

就算开启了tree-shaking, import _ from "lodash" 也不如 import flatten from "lodash/flatten" size 小

// mode dev  400k 
// mode prod 73k 
import _ from "lodash"

// mode dev 21k 
// mode prod 3k 
import flatten from "lodash/flatten"

const arr = [1, [2]];
console.log( _.flatten(arr))

更多

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值