Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

webpack相关 #33

Open
LingYanSi opened this issue Jul 13, 2016 · 11 comments
Open

webpack相关 #33

LingYanSi opened this issue Jul 13, 2016 · 11 comments

Comments

@LingYanSi
Copy link
Owner

webpack打包生成文件详解

源文件

import 'module/bitch'
window.y = 'fuck'

生成文件

/******/ (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] = {
/******/            exports: {},
/******/            id: moduleId,
/******/            loaded: false
/******/        };

/******/        // Execute the module function
/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

/******/        // Flag the module as loaded
/******/        module.loaded = 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;

/******/    // __webpack_public_path__
/******/    __webpack_require__.p = "";

/******/    // Load entry module and return exports
/******/    return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {

    'use strict';

    __webpack_require__(3);

    window.y = 'fuck';

/***/ },
/* 1 */,
/* 2 */,
/* 3 */
/***/ function(module, exports) {

    "use strict";

/***/ }
/******/ ]);

根据打包后的文件可以看出,生成的是一个立即执行函数,参数是,此文件的所有以来文件
假设a.js依赖了b.js c.js他们又都依赖了d.js,但d.js只会被加载一次?

@LingYanSi
Copy link
Owner Author

LingYanSi commented Jul 14, 2016

看webpack的立即执行函数,当require(module)的时候
会先从缓存中查询,如果export已经被缓存,就从缓存拿,没有缓存就执行一边依赖的module,然后把exportZ缓存起来
因此,即便一个module被多次依赖,函数也只会被执行一次

@LingYanSi
Copy link
Owner Author

路径查找规则

import XX from 'module/xx'

错误提示

ERROR in ./build/react/pages/Main/index.js
Module not found: Error: Cannot resolve 'file' or 'directory' D:\work\mock\build\react\module/xx in D:\work\mock\build\react\pages\Main
resolve file
  D:\work\mock\build\react\module\xx.js doesn't exist
  D:\work\mock\build\react\module\xx.jsx doesn't exist
  D:\work\mock\build\react\module\xx.scss doesn't exist
  D:\work\mock\build\react\module\xx is not a file
resolve directory
  D:\work\mock\build\react\module\xx\package.json doesn't exist (directory description file)
  directory default file index
    resolve file index in D:\work\mock\build\react\module\xx
      D:\work\mock\build\react\module\xx\index.js doesn't exist
      D:\work\mock\build\react\module\xx\index doesn't exist
      D:\work\mock\build\react\module\xx\index.jsx doesn't exist
      D:\work\mock\build\react\module\xx\index.scss doesn't exist
[D:\work\mock\build\react\module\xx.js]
[D:\work\mock\build\react\module\xx.jsx]
[D:\work\mock\build\react\module\xx.scss]
[D:\work\mock\build\react\module\xx\index.js]
[D:\work\mock\build\react\module\xx\index]
[D:\work\mock\build\react\module\xx\index.jsx]
[D:\work\mock\build\react\module\xx\index.scss]
 @ ./build/react/pages/Main/index.js 27:10-30

由以上可知,webpack先看module/xx对应的js/jsx/scss/无后缀文件是否存在,如果不存在,看看xx是不是一个文件夹,如果是就看看module/xx/index对应的js/jsx/css/无后缀文件是否存在
因此在引用文件的时候就不用加上丑陋的index.js了

import XX from 'module/xx'
import XX from 'module/xx/index.js'

@LingYanSi
Copy link
Owner Author

LingYanSi commented Jul 27, 2016

写一个webpack插件

待补充
教程
官方文档

@LingYanSi
Copy link
Owner Author

webpack 使用less postcss的时候,loader的加载顺序有要求

{ test: /\.less$/, loader: `style!css!postcss!less` },

如果是这样

{ test: /\.less$/, loader: `style!css!less!postcss` },

回到这postcss无法解析注释符号 *** // ***

@LingYanSi
Copy link
Owner Author

LingYanSi commented Feb 5, 2017

重启webpack

webpack的启动方式

  • webpack
  • npm run pack

以上两种方式本质都是直接启动webpack
不过既然要重启webpack,那就需要有一个webpack的守护程序

守护程序需要做的一件事情就是,在特定情况下重启webpack
这个特定情况,一般是指:添加、删除entry文件
因此需要对文件夹进行监控

还需注意的是,当变化比较频繁的时候,要避免webpack多次启动

let chokidar = require('chokidar')
let child_process = require('child_process')
let exec = child_process.exec

let watcherReady = false
// 用来监控文件夹变化
let watcher = chokidar.watch('./react/pages', {
    ignored: /(^|[\/\\])\../,
    persistent: true
});

// 新增或者删除一个指定文件,就重启webpack
watcher.on('add', path => {
    if (watcherReady && path.toLowerCase().endsWith('/view.js')) {
        // 杀掉当前子进程
        // 启动一个新进程
        console.log('新增view: 重启webpack打包')
        restartWebpack()
    }
}).on('unlink', path => {
    if (watcherReady && path.toLowerCase().endsWith('/view.js')) {
        console.log('删除view: 重启webpack打包')
        restartWebpack()
    }
})
.on('ready', ()=>{
    watcherReady = true
})

// 启动webpack
function startWebpack(){
    let child = exec('npm run __dev__')
    child.stdout.on('data', data=>{
        console.log(data)
    })

    console.log('>> child_process id: ', child.pid)
    // setTimeout(()=>{
    //     console.log('杀掉子进程')
    //     child.kill()
    // }, 3000)

    let resolve
    // 监听退出,然后关闭掉后台运行的webpack
    child.on('exit', ()=>{
        console.log('webpack 停止了')
        // 因为webpack是在后台监听,因此需要手动关闭
        exec(`ps aux | grep node | grep webpack`, (err, stdout, stdin)=>{
            Promise.all(
                // 找到__dir下webpack进程的pid,然后将其kill
                stdout.split('\n').filter(i => i.includes(__dirname)).forEach(item=>{
                    let pid = item.split(/\s+/)[1]
                    console.log('pid: ', pid)
                    return new Promise((res, rej) => {
                        exec(`kill -9 ${pid}`, (err, stdout, stdin)=>{
                            console.log('??????')
                            res()
                        }).on('error', ()=>{
                            res()
                        })
                    })
                })
            ).then(msg => {
                resolve && resolve('stop webpack success')
            }).catch(err => {
                resolve && resolve('stop webpack fail')
            })
        })
    })

    return {
        // 停止webpack
        stop(){
            child.kill()
            return new Promise(res => {
                resolve = res
            })
        }
    }
}

function restartWebpack(){
    webpackProcess && webpackProcess.stop().then(msg => {
        console.log(msg)
        if (!webpackProcess) {
            webpackProcess = startWebpack()
        }
    })
    // 确保startWebpack不会重复执行
    webpackProcess = null
}

let webpackProcess = startWebpack()

@LingYanSi
Copy link
Owner Author

LingYanSi commented Feb 14, 2017

打包输出md5文件名频繁变更问题

当我们新增、删除、修改文件,未依赖此文件的入口文件的output文件名也会变更
这当然不是我们所希望的,A的依赖文件未变更,打包文件名从直观角度出发,是不应该变更的

  • 对于webpack 1.0 来说,需要手动添加一个plugin
new webpack.optimize.OccurrenceOrderPlugin()
  • 对于webpack 2.0,系统已默认开启了此项功能

然而!!!OccurrenceOrderPlugin并无卵用!!!
因为webpack自己的chunkhash是根据文件内容来生成md5,并且一个webpack打包项目,共用一个统一的module系统,因此output生成文件内会有 /**0**/这些东西
但问题是当新增或删除依赖文件时,/**0**/类似的东西会随之增减改变,然后文件的md5就变了,即便这个文件的依赖并没有变化!!!WTF

终极解决方案,

使用 webpack-md5-hash,它覆盖了默认的chunkhash的行为,以真正有用的字符串来计算hash,忽略系统同意module对生成文件的影响
源码在这儿,很简单

@LingYanSi
Copy link
Owner Author

LingYanSi commented Feb 14, 2017

webpack 2.0新功能及特性

let webpack = require('webpack')
let WebpackOnBuildPlugin = require('on-build-webpack');
// 使用ExtractTextPlugin把css抽出来
let ExtractTextPlugin = require("extract-text-webpack-plugin");

let WebpackPathOrderPlugin = require('path-order-webpack-plugin');

let WebpackMd5Hash = require('webpack-md5-hash')

let notifier = require('node-notifier');

let autoprefixer = require('autoprefixer')
let exec = require('child_process').exec

let path = require('path')
let fs = require('fs')

function webpackDone(title, message, sound) {
    notifier.notify({
        title: title,
        message: message,
        sound: sound,
        icon: path.resolve(__dirname, '/Users/zikong/LingYanSi.github.io/images/wangsitu.jpg')
    }, function(err, respond) {
        if (err)
            console.error(err);
        }
    );
}

let entry = {
    '/rv': './Rv/index.js',
    '/app': './test/app.js',
    // '/div': './div/index.js'
}

exec('\\rm -r dist/ ')

module.exports = {
    // 是否缓存
    cache: true,
    // 是否监听文件变化
    watch: true,
    // 入口配置
    entry,
    // 输出配置
    output: {
        // 输出路径
        path: 'dist',
        // chunckhash 是指文件最终的md5
        filename: "[name].[chunkhash:9].js",
        // 块文件名称?
        chunkFilename: "[name].js"
    },
    module: {
        rules: [
            // 对js/jsx文件的处理
            {
                test: /\.(js|jsx)$/,
                use: [
                    {
                        loader: 'babel-loader',
                        options: {
                            presets: ['es2015'], // 把es2015转译成es5,这么做的弊端在于有些浏览器已经支持了新特性,却不能使用
                            plugins: ['transform-object-rest-spread', 'transform-class-properties', 'transform-decorators-legacy']
                        }
                    }
                ]
            }, {
                test: /\.scss$/,
                use: [
                    'style-loader',
                    'css-loader', {
                        loader: 'postcss-loader',
                        options: {
                            plugins: function() {
                                return [//   require('precss'),
                                    require('autoprefixer')];
                            }
                        }
                    },
                    'sass-loader'
                ]
            }
        ]
    }, 
    externals: {},
    resolve: {
        extensions: [
            '.js', '.jsx', '.scss', '.css'
        ],
        // bieming
        alias: {}
    },
    target: "web", // enum
    // the environment in which the bundle should run
    // changes chunk loading behavior and available modules

    devtool: "source-map", // enum
    // enhance debugging by adding meta info for the browser devtools
    // source-map most detailed at the expense of build speed.

    context: __dirname, // string (absolute path!)
    // the home directory for webpack
    // the entry and module.rules.loader option
    //   is resolved relative to this directory

    // 扩展名,按先后顺序尝试查找
    // 插件
    plugins: [
        new WebpackMd5Hash(),
        new WebpackPathOrderPlugin(),
        // new ExtractTextPlugin("./../css/app.css"),
        // 压缩js文本8
        // new webpack.optimize.UglifyJsPlugin({
        //     compress: {
        //         warnings: false
        //     }
        // }),
        // 打印日志
        new WebpackOnBuildPlugin(function(stats) {
            var compilation = stats.compilation;
            var errors = compilation.errors;

            if (errors.length > 0) {
                var error = errors[0];
                webpackDone(error.name, error.message, 'Glass');
            } else {
                var message = 'takes ' + (stats.endTime - stats.startTime) + 'ms';

                var warningNumber = compilation.warnings.length;
                if (warningNumber > 0) {
                    message += ', with ' + warningNumber + ' warning(s)';
                }

                webpackDone('webpack building done', message);
            }
        })

    ].filter(i => i)
}

@LingYanSi
Copy link
Owner Author

完了,感觉16年自己比19年的我还要喜欢技术

 (function(modules) { // webpackBootstrap
 	// install a JSONP callback for chunk loading
 	function webpackJsonpCallback(data) {
 		var chunkIds = data[0];
 		var moreModules = data[1];
 		var executeModules = data[2];

 		// add "moreModules" to the modules object,
 		// then flag all "chunkIds" as loaded and fire callback
 		var moduleId, chunkId, i = 0, resolves = [];
 		for(;i < chunkIds.length; i++) {
 			chunkId = chunkIds[i];
 			if(installedChunks[chunkId]) {
 				resolves.push(installedChunks[chunkId][0]);
 			}
 			installedChunks[chunkId] = 0;
 		}
 		for(moduleId in moreModules) {
 			if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
 				modules[moduleId] = moreModules[moduleId];
 			}
 		}
 		if(parentJsonpFunction) parentJsonpFunction(data);

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

 		// add entry modules from loaded chunk to deferred list
 		deferredModules.push.apply(deferredModules, executeModules || []);

 		// run deferred modules when all chunks ready
 		return checkDeferredModules();
 	};
 	function checkDeferredModules() {
 		var result;
 		for(var i = 0; i < deferredModules.length; i++) {
 			var deferredModule = deferredModules[i];
 			var fulfilled = true;
 			for(var j = 1; j < deferredModule.length; j++) {
 				var depId = deferredModule[j];
 				if(installedChunks[depId] !== 0) fulfilled = false;
 			}
 			if(fulfilled) {
 				deferredModules.splice(i--, 1);
 				result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
 			}
 		}
 		return result;
 	}

 	// The module cache
 	var installedModules = {};

 	// object to store loaded and loading chunks
 	// undefined = chunk not loaded, null = chunk preloaded/prefetched
 	// Promise = chunk loading, 0 = chunk loaded
 	var installedChunks = {
 		"manifest": 0
 	};

 	var deferredModules = [];

 	// script path function
 	function jsonpScriptSrc(chunkId) {
 		return __webpack_require__.p + "" + ({"uploadImg":"uploadImg","ADPcategorySelect":"ADPcategorySelect","categorySelect":"categorySelect","draftEditor":"draftEditor","bankListAddCard":"bankListAddCard","modules/uploadVideo/core":"modules/uploadVideo/core","addressConfig":"addressConfig"}[chunkId]||chunkId) + "." + {"uploadImg":"3e3b9ca65","ADPcategorySelect":"9dbce0ffc","categorySelect":"e1e962ee1","draftEditor":"1dee8d532","bankListAddCard":"d12207fad","modules/uploadVideo/core":"1f0c2441e","addressConfig":"a58eadf41"}[chunkId] + ".js"
 	}

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

 	// This file contains only the entry chunk.
 	// The chunk loading function for additional chunks
 	__webpack_require__.e = function requireEnsure(chunkId) {
 		var promises = [];


 		// JSONP chunk loading for javascript

 		var installedChunkData = installedChunks[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
 				var promise = new Promise(function(resolve, reject) {
 					installedChunkData = installedChunks[chunkId] = [resolve, reject];
 				});
 				promises.push(installedChunkData[2] = promise);

 				// start chunk loading
 				var head = document.getElementsByTagName('head')[0];
 				var script = document.createElement('script');
 				var onScriptComplete;

 				script.charset = 'utf-8';
 				script.timeout = 120;
 				if (__webpack_require__.nc) {
 					script.setAttribute("nonce", __webpack_require__.nc);
 				}
 				script.src = jsonpScriptSrc(chunkId);

 				onScriptComplete = function (event) {
 					// avoid mem leaks in IE.
 					script.onerror = script.onload = null;
 					clearTimeout(timeout);
 					var chunk = installedChunks[chunkId];
 					if(chunk !== 0) {
 						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;
 							chunk[1](error);
 						}
 						installedChunks[chunkId] = undefined;
 					}
 				};
 				var timeout = setTimeout(function(){
 					onScriptComplete({ type: 'timeout', target: script });
 				}, 120000);
 				script.onerror = script.onload = onScriptComplete;
 				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;
 		var ns = Object.create(null);
 		__webpack_require__.r(ns);
 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
 		if(mode & 2 && typeof value != 'string') for(var 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) {
 		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 = "";

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

 	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;


 	// run deferred modules from other chunks
 	checkDeferredModules();
 })
 ([]);

jsonpArray.push指向的是webpackJsonpCallback

@LingYanSi
Copy link
Owner Author

其他entry的带包结果

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["actTmp"],{

/***/ "wpt":
/*!*****************************!*\
  !*** external "window.WPT" ***!
  \*****************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = window.WPT;

/***/ })

},[["./src/actComp/actViews/View.js","manifest"]]]);

如果chunkId已经被缓存的话,指定逻辑就不会被执行?

@LingYanSi
Copy link
Owner Author

连续调用只会执行一次

webpackJsonp.push([['test'], { "/test3": () => { console.log(111) } }, [[ "/test3" ]]])

webpackJsonp.push([['test'], { "/test3": () => { console.log(111) } }, [[ "/test3" ]]])

image

@LingYanSi
Copy link
Owner Author

LingYanSi commented Nov 3, 2021

splitChunks

https://webpack.js.org/plugins/split-chunks-plugin/ 请仔细阅读文档

splitChunks: {
    // name: 'vendor',
    cacheGroups: {
        default: false, // 阻止dynamic import的资源进入vendor
        vendor: {
            minSize: 0,
            chunks: 'initial',
            name: 'vendor',
            enforce: true,
            test(module) {
                const userRequest = module.userRequest;
                if (!userRequest) return false;
                return vendorModulesRegex.test(userRequest) || userRequest.startsWith(packageDir) || userRequest.startsWith(mainDir);
            },
        },
    },
}

对于一般项目使用webpack的默认配置即可

但有的时候,我们想精确控制哪些文件(npm包)可以进入vendor,那就需要做一些额外配置

  • splitChunks不要指定name: vendor,否则共享的东西都会进入vendor,这不是我们想要的
  • 设置default: false以解除默认配置,否则动态加载共享的文件会进入vendor
  • 指定chunks: initial表示不能异步加载
  • 添加test函数,只有userRequest通过校验才能进入vendor
  • 如果import('./内容中有依赖node_modeules中的haha')haha会被打包为vendors~haha

@LingYanSi LingYanSi changed the title webpack详解 webpack相关 Nov 3, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant