├── .eslintrc.json ├── .gitignore ├── README.md ├── expose.md ├── extractPublic.md ├── introduce.md ├── lib ├── async.case.1.js ├── async.start.1.js ├── asyncParalleHook.case.js ├── asyncParalleHook.start.js ├── asyncSeriesHook.case.js ├── asyncSeriesHook.start.js ├── asyncSeriesWaterfallHook.case.js ├── asyncSeriesWaterfallHook.start.js ├── syncBailHook.js ├── syncLoopHook.js └── syncWaterfallHook.js ├── multiEntry.md ├── pack.md ├── packPicture.md ├── package.json ├── postcss.config.js ├── server.js ├── src ├── a.js ├── index.css ├── index.html ├── index.js ├── index.less ├── source.js └── tianfuluo.png ├── webpack.base.js ├── webpack.dev.js └── webpack.prod.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | // { 2 | // "parserOptions": { 3 | // "ecmaVersion": 5, 4 | // "sourceType": "script", 5 | // "ecmaFeatures": {} 6 | // }, 7 | // "rules": { 8 | // "constructor-super": 2, 9 | // "for-direction": 2, 10 | // "getter-return": 2, 11 | // "no-case-declarations": 2, 12 | // "no-class-assign": 2, 13 | // "no-compare-neg-zero": 2, 14 | // "no-cond-assign": 2, 15 | // "no-console": 2, 16 | // "no-const-assign": 2, 17 | // "no-constant-condition": 2, 18 | // "no-control-regex": 2, 19 | // "no-debugger": 2, 20 | // "no-delete-var": 2, 21 | // "no-dupe-args": 2, 22 | // "no-dupe-class-members": 2, 23 | // "no-dupe-keys": 2, 24 | // "no-duplicate-case": 2, 25 | // "no-empty": 2, 26 | // "no-empty-character-class": 2, 27 | // "no-empty-pattern": 2, 28 | // "no-ex-assign": 2, 29 | // "no-extra-boolean-cast": 2, 30 | // "no-extra-semi": 2, 31 | // "no-fallthrough": 2, 32 | // "no-func-assign": 2, 33 | // "no-global-assign": 2, 34 | // "no-inner-declarations": 2, 35 | // "no-invalid-regexp": 2, 36 | // "no-irregular-whitespace": 2, 37 | // "no-mixed-spaces-and-tabs": 2, 38 | // "no-new-symbol": 2, 39 | // "no-obj-calls": 2, 40 | // "no-octal": 2, 41 | // "no-redeclare": 2, 42 | // "no-regex-spaces": 2, 43 | // "no-self-assign": 2, 44 | // "no-sparse-arrays": 2, 45 | // "no-this-before-super": 2, 46 | // "no-undef": 2, 47 | // "no-unexpected-multiline": 2, 48 | // "no-unreachable": 2, 49 | // "no-unsafe-finally": 2, 50 | // "no-unsafe-negation": 2, 51 | // "no-unused-labels": 2, 52 | // "no-unused-vars": 2, 53 | // "no-useless-escape": 2, 54 | // "require-yield": 2, 55 | // "use-isnan": 2, 56 | // "valid-typeof": 2 57 | // }, 58 | // "env": {} 59 | // } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /dist/ 3 | /bundle/ 4 | /package-lock.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 学习webpack的配置 2 | 3 | - webpack基础配置,webpack-dev-server配置、css-loader & style-loader配置 [introduce.md](https://github.com/typhoonIscoming/webpack-basic/blob/master/introduce.md) 4 | - webpack打包后的代码结构解析 [pack.md](https://github.com/typhoonIscoming/webpack-basic/blob/master/pack.md) 5 | - 将模块暴露到全局变量window中 [expose.md](https://github.com/typhoonIscoming/webpack-basic/blob/master/expose.md) 6 | - 对于图片的打包处理及loader的配置 [packpicture.md](https://github.com/typhoonIscoming/webpack-basic/blob/master/packPicture.md) 7 | - 多入口页面的配置及source-map配置、实时打包代码、跨域解决、webpack优化 [multiEntry.md](https://github.com/typhoonIscoming/webpack-basic/blob/master/multiEntry.md) 8 | - 多页应用抽离公共模块、懒加载、热更新、tapable实现 [extractPublic.md](https://github.com/typhoonIscoming/webpack-basic/blob/master/extractPublic.md) 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /expose.md: -------------------------------------------------------------------------------- 1 | 2 | ## 将引入的插件暴露到全局变量window中 3 | - 在文件中引入插件 如: import $ from 'juqery' 4 | - console.log(window.$) // undefine 5 | - 此时就需要用到expose-loader。它是专门将插件暴露到全局中 6 | - npm i expose-loader 7 | - 使用 如引入jquery: import $ from 'expose-loader?$!jquery' 8 | - expose-loader 暴露全局的loader (内联loader) 9 | - pre(前置loader) normal(普通loader) 内联loader post(后置loader) 10 | - 也可以配置到webpack中 11 | ``` 12 | module: { 13 | rules: [ 14 | { 15 | test: require.resolve('juqery'), 16 | use: 'expose-loader?$', 17 | }, 18 | ] 19 | } 20 | ``` 21 | 22 | ### 在每个模块中注入$对象 23 | 24 | - 在webpack配置中引入webpack, webpack提供了一个provideplugin方法,在每个模块中都注入$ 25 | ``` 26 | plugins: [ 27 | new webpack.ProvidePlugin({ 28 | $: 'jquery', 29 | }), 30 | ] 31 | ``` 32 | - 但是这样就没有将jquery暴露在window对象中了 33 | 34 | - 如果在文件中通过import $ from 'jquery',同时在html页面中通过cdn等方式又引入了jquery,, 这样,webpack还是会将jquery打包,这样就造成打包文件很大。 35 | - 可以添加配置 externals 36 | ``` 37 | externals: { 38 | jquery: '$' 39 | } 40 | ``` 41 | - 这样jquery就不会被进行打包 42 | - 现在,我们就知道有几种引入第三方插件的方式 43 | 1. 使用expose-loader, 暴露到window上 44 | 2. providePlugin,给每个模块提供一个$ 45 | 3. 引入cdn,但是不打包 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /extractPublic.md: -------------------------------------------------------------------------------- 1 | ## 多页应用抽离公共代码 2 | 3 | - 如:有a.js 和 b.js两个公共模块,在index.js中和other.js两个文件中都需要这两个模块,那么将a b缓存起来,就不需要都把公共模块打包进入口代码中了 4 | ``` 5 | entry: { // 多页面 6 | index: './src/index.js', 7 | other: './src/other' 8 | }, 9 | output: { // 出口页面 10 | filename: '[name].js', 11 | path: path.resolve(__dirname, 'dist'), 12 | }, 13 | ------ 此时,如果index.js和other.js中引入了a b模块,那么这两个模块的代码都分别被打进了这两个文件中 14 | // 增加 optimization 15 | optimization: { // 优化 在webpack4之前叫 commonChunkPlugins 16 | splitChunks: { // 分割代码块,把公共的分离出来 17 | cacheGroups: { // 缓存组 18 | common: { // 公共模块 公共模块满足的条件就进行抽离 19 | minSize: 0, // 大于0字节 20 | minChuncks: 2, // 公共模块被引入多少次,就单独抽离出来 21 | chunks: 'initial', // 从哪里开始就抽离代码,还有异步模块 22 | }, 23 | vendor: { // 第三方 第三方的模块也单独抽离出来 24 | priority: 1, // 设置权重,先抽取第三方的模块 25 | test: /node_modules/, // 只要引用了node_modules中的文件,就单独抽离出来 26 | minSize: 0, // 大于0字节 27 | minChuncks: 2, // 公共模块被引入多少次,就单独抽离出来 28 | chunks: 'initial', // 从哪里开始就抽离代码,还有异步模块 29 | }, 30 | }, 31 | }, 32 | } 33 | ``` 34 | ## 懒加载 35 | 36 | - 在入口文件中,一个点击事件中使用import引入模块 37 | ``` 38 | // 安装@babel/plugin-syntax-dynamic-import插件 39 | // webpack.base.js 40 | plugins: ['@babel/plugin-syntax-dynamic-import'], 41 | // index.js 42 | btn.addEventListener('click', function(){ 43 | import('./source.js').then((res) => { // es6草案中的语法,用jsonp实现动态加载文件 44 | console.log('print lazy load module', res) 45 | }) 46 | }) 47 | // Add @babel/plugin-syntax-dynamic-import (https://git.io/vb4Sv) to the 'plugins' section of your Babel config to enable parsing 48 | 49 | // 添加配置 50 | module: { 51 | rules: [ 52 | { 53 | test: /\.js$/, 54 | use: { 55 | loader: 'babel-loader', 56 | options: { 57 | presets: ['@babel/preset-env'], 58 | plugins: [ 59 | '@babel/plugin-syntax-dynamic-import' 60 | ] 61 | }, 62 | }, 63 | } 64 | ], 65 | } 66 | ``` 67 | 68 | ## 热更新 69 | 70 | - 在之前,当文件被更改之后,它会重新加载所有的模块,我们希望我们改变了某个模块,就只重新加载这个模块 71 | - 在devServer: { hot: true }, // 它需要一个插件来支持 72 | - webpack.HotModuleReplacementPlugin() // 热更新插件 73 | ``` 74 | import source from './source' 75 | if(module.hot) { // 如果模块支持热更新 76 | module.hot.accept('./source', () => { // 在完成热更新的回调 77 | // 在完成热更新后,重新加载这个模块,这样页面就拿到更新后的source,同时不会刷新页面 78 | let source = require('./source'); // import只能用在页面顶部 79 | console.log('file is been reload', source) 80 | }) 81 | } 82 | ``` 83 | 84 | ## tapable 85 | - webpack本质上是一种事件流机制,它的工作流程就是将各个插件串联起来,而实现这一核心就是tapable,它有点类似于node的events库,核心原理也是依赖于发布订阅模式 86 | ``` 87 | // npm i tapable -d 88 | const { 89 | syncHook, 90 | syncBailHook, 91 | syncWaterfallHook, 92 | syncLoopHook, 93 | asyncParalleHook, 94 | asyncParalleBailHook, 95 | asyncSeriesHook, 96 | asyncSeriesBailHook, 97 | asyncSeriesWaterfallHook, 98 | } = require('tapable') 99 | 100 | ``` 101 | - 手动实现tapable[./lib/async.case.1.js](https://github.com/typhoonIscoming/webpack-basic/blob/master/lib/async.case.1.js) 102 | - tapable的源码的实现[./lib/async.start.1.js](https://github.com/typhoonIscoming/webpack-basic/blob/master/lib/async.start.1.js) 103 | - ayncBailHook(同步保险钩子),在同步钩子中,可以决定是否向下执行 104 | - 只要任何一个监听函数返回了(非undefined),就中断后面函数的执行 105 | - ayncBailHook的实现原理[./lib/syncBailHook.js](https://github.com/typhoonIscoming/webpack-basic/blob/master/lib/syncBailHook.js) 106 | - ayncWaterfallHook(同步瀑布钩子),上一个函数执行的返回,会传递给下一个函数, syncWaterfallHook 实现原理[./lib/syncWaterfallHook.js](https://github.com/typhoonIscoming/webpack-basic/blob/master/lib/syncWaterfallHook.js) 107 | - ayncLoopHook(同步循环钩子),当某个函数返回的是(非undefined),那么这个函数会多次执行,实现原理[./lib/syncLoopHook.js](https://github.com/typhoonIscoming/webpack-basic/blob/master/lib/syncLoopHook.js) 108 | 109 | ## 异步的钩子函数,同时执行多个请求。(串行、并行) 110 | - 串行:第一个异步执行完,再执行第二个 111 | - 并行:需要等待所有的并发的异步事件执行后再执行回调方法 112 | - asyncParalleHook: 异步并行钩子 113 | - 执行方法分为tap注册,和tapAsync 114 | - asyncParalleHook应用 [asyncParalleHook.case.js](https://github.com/typhoonIscoming/webpack-basic/blob/master/lib/asyncParalleHook.case.js) 115 | - 其中任何一个注册事件没有执行完,都不会触发最后的回调。asyncParalleHook是通过判断执行的注册事件的回调次数是否和注册事件个数相等 116 | - asyncParalleHook 的实现原理 [asyncParalleHook.start.js](https://github.com/typhoonIscoming/webpack-basic/blob/master/lib/asyncParalleHook.start.js) 117 | 118 | 119 | - tapable库中有三种注册事件方法。 120 | 1. tap:同步注册 121 | 2. tapAsync: 异步注册 122 | 3. tapPromise: promise注册 123 | - 调用也分别分为:call callAsync promise 124 | 125 | ## 异步串行钩子函数 asyncSeriesHook 126 | - asyncSeriesHook 的使用 [asyncSeriesHook.case.js](https://github.com/typhoonIscoming/webpack-basic/blob/master/lib/asyncSeriesHook.case.js) 127 | - asyncSeriesHook 的原理 [asyncSeriesHook.start.js](https://github.com/typhoonIscoming/webpack-basic/blob/master/lib/asyncSeriesHook.start.js) 128 | 129 | ## 异步串行瀑布钩子函数 asyncSeriesWaterfallHook 130 | - asyncSeriesWaterfallHook 的使用 [asyncSeriesWaterfallHook.case.js](https://github.com/typhoonIscoming/webpack-basic/blob/master/lib/asyncSeriesWaterfallHook.case.js) 131 | - asyncSeriesWaterfallHook 的原理 [AsyncSeriesWaterfallHook.start.js](https://github.com/typhoonIscoming/webpack-basic/blob/master/lib/asyncSeriesWaterfallHook.start.js) 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /introduce.md: -------------------------------------------------------------------------------- 1 | ## webpack可以看作是一个模块打包机。他做的事情就是分析你的项目结构,找到javascript模块以及其他浏览器不能直接运行的拓展语言(scss, typescript),并将其打包成合适的格式以供浏览器使用 2 | 3 | - 可以做的事:文件优化、代码分割、模块合并、自动刷新、代码校验、自动发布。 4 | 5 | - 需要掌握的基础: 需要node基础、以及使用npm、掌握es6语法 6 | 7 | - 需要掌握webpack的基础: webpack常见配置、webpack高级配置、webpack优化配置、ast抽象语法树、webpack中的tapable、掌握webpack流程,手写webpack、手写webpack常见loader、手写常见的plugin 8 | 9 | ## webpack安装 10 | - 安装本地webpack 11 | - webpack webpack-cli -D 12 | 13 | ## webpack可以进行0配置 14 | - 打包工具 -> 输出最后结果(js模块) 15 | 16 | - 执行webpack命令,可以使用webpack提供的npx webpack命令 或者 npm run dev 17 | - 执行这个命令,它会默认去找node_modules下的.bin文件夹中的webpack.cmd 18 | - webpack默认执行的打包地址是src/index.js,所以要把代码放在src目录下 19 | 20 | - 在index.js中导入a.js,在浏览器中是不会被浏览器所执行。webpack帮我们解析js模块,查找所以相关的模块,并打包成一个文件,解决了浏览器的require的问题,相当于它自己实现了模块化的机制 21 | 22 | ## 手动配置webpack 23 | 24 | - 默认配置文件的名字 webpack.config.js 25 | - 在node_modules中的webpack-cli -> bin -> config-yargs.js(line 74)配置文件可以是webpack.config.js 或者 webpackfile.js,一般我们选用webpack.config.js,当然也可以不用这两个名字 26 | - 可以设置成其他名字如:webpack.config.my.js, 在package.json中的scripts命令中配置“named: webpack --config webpack.config.my.js”;这样在执行webpack的时候,就知道配置文件是重新改名的这个文件了 27 | 28 | 29 | ## 我们需要在本地启一个服务,可以使用webpack-dev-server 30 | 31 | - 在package.json中配置脚本,用以启用webpack-dev-server;"dev": "webpack-dev-server" 32 | - 在webpack.config.js中配置devServer: {} 33 | 34 | ## 在webpack中配置css模块 35 | 36 | - webpack默认是支持js模块的,我们希望css文件也变成一个模块。在index.js文件中引入require('./index.css),此时webpack会报错Module parse failed, You may need an appropriate loader to handle this file type 37 | - 模块解析失败,你可能需要一个合适的loader去处理这个文件 38 | - 具体配置查看webpack.config.js中的module.rules 39 | 40 | 41 | ## 在webpack引入css模块后,都是引入样式到style标签中,这样如果样式很多的化,可能会造成页面阻塞的问题,所以我们希望能放在link标签中 42 | - 这就需要用到mini-css-extract-plugin插件,它是专门用来抽离css文件的插件 npm i mini-css-extract-plugin -d 43 | - 在webpack.config.js中引入 const miniCssExtractPlugin = require('mini-css-extract-plugin') 44 | - 在模块的的rules中使用这个插件的loader,表示将这个css模块通过link标签添加到页面中 45 | - 将style-loader替换成miniCssExtractPlugin.loader即可这样打包之后的html页面就有link标签引入的样式了,并且在打包的文件中多了一个样式的文件 46 | - 注:在使用miniCssExtractPlugin时,会给一个模块设置filename,多个模块要设置不同的名字,可以多创建几个miniCssExtractPlugin实例 47 | 48 | ## 我们希望在生成的css模块中,能自动加上浏览器前缀,这时就需要用到autoprefixer,处理这个插件也需要一个loader,postcsss-loader 49 | - 在编译成css文件之前使用这个loader,即在需要这个插件的模块中,在编译成css文件之前,对样式模块文件先做处理,即在css-loader后面添加这个loader 50 | - 需要在项目根目录添加一个配置文件。因为此时,webpack还不知道是不是使用autoprefixer这个插件。如果不加,会报一个 No PostCSS Config found in: /Users/xiehang/Desktop/vue-prictise/webpack-basic/src 的错误 51 | - 添加postcss.config.js文件 module.exports = { plugins: [require('autoprefixer')] } 即可 52 | 53 | ## 压缩生成的css文件 54 | - 如果使用mini-css-extract-plugin,那么我们就要手动去压缩js文件 55 | - webpack4提供了一个优化项 optimization ,下载 optimize-css-assets-webpack-plugin 插件 56 | - 安装了 optimize-css-assets-webpack-plugin 这个插件,就必须安装 uglifyjs-webpack-plugin 插件,否则,js就不会被压缩 57 | - 注,在使用过程中,报了这个错误 58 | - ERROR in main.63f76874.js from UglifyJs 59 | - Unexpected token: name (A) [./src/index.js:2,0][main.63f76874.js:92,4] 60 | - 是在 UglifyJs 处理js报错,还不支持es6 命名的变量和语法 61 | - 后期会进行babel的配置 62 | - 这样打包出来的文件 css js都被压缩了 63 | 64 | ## 每次打包生成一个新的dist文件,将上一次的文件夹删除 65 | - npm i clean-webpack-plugin -d 66 | - plugins: [ new CleanWebpackPlugin([ "./dist"]) ] 67 | 68 | ## 拷贝插件,copy-webpack-plugin, 如果要将某一个文件夹下的文件也拷贝到dist目录下,就可以使用这个插件 69 | - npm i copy-webpack-plugin -d 70 | - plugins: [ new CopyWebpackPlugin({ from: 'doc', to: './dist' }) ], // 这样doc目录下的文件就会被拷贝到dist目录中 71 | 72 | ## 版权声明插件 bannerPlugin,这是webpack内置的插件 73 | - plugin: [ new webpack.bannerPlugin('this is made by xie') ], // 这样,在打包后的js模块前面就会加上这个注释 74 | 75 | ## 处理js模块 76 | ### 将es6 转成 es5 77 | 78 | - 需要用到babel 79 | - 安装babel npm i babel-loader @babel/core(babel核心模块) @babel/preset-env(转换所有的js语法) -d 80 | - 在js模块中配置js规则 81 | - 当然更高级的语法,还需要添加其他的插件 如类 class 82 | ``` 83 | options: { 84 | plugins: [ 85 | ["@babel/plugin-proposal-decorators", { "legacy": true }], // 类的装饰器 86 | ["@babel/plugin-proposal-class-properties", { "loose" : true }], // 解析类class 的插件 87 | ] 88 | } 89 | ``` 90 | 91 | - 对于es6中class generator等语法,这是内置的API,在将js模块转换成es5时,它也不会转换成es5的代码,所以此时,需要用到 92 | - @babel/plugin-transform-runtime 这是一个开发依赖 93 | - npm install --save @babel/runtime 94 | - 并且会将公共的方法抽离出来,这样在转换不同模块的相同的语法和API时,就不会有冗余代码 95 | -如果遇到更高级的代码,如'abcde'.includes('a'), includes是es7的语法,对于实例上的方法,babel是不会转换的,这时就需要ployfill这个包了 96 | - @babel/polyfill 这是一个生产依赖,上线也需要带着这个 97 | 98 | ## 添加eslint代码规范 99 | - 在eslint官网上 [demo](https://eslint.org/demo/),可以配置你的代码中需要的代码规范,然后下载规范的json文档,再添加到项目中 100 | - 安装eslint, npm i eslint eslint-loader 101 | 102 | 103 | -------------------------------------------------------------------------------- /lib/async.case.1.js: -------------------------------------------------------------------------------- 1 | let { SyncHook } = require('tapable') 2 | 3 | class Lession{ 4 | constructor() { 5 | this.hooks = { // 订阅一些钩子,然后用lession的实例来启动这些钩子 6 | arch: new SyncHook(['name']), // 定义了一个钩子,可以订任意个 7 | } 8 | } 9 | tap() { // 注册监听函数 10 | // 钩子上有一个tap注册事件 11 | this.hooks.arch.tap('node', function(name ) { // 回调函数 12 | console.log('node watched', name) 13 | }); 14 | this.hooks.arch.tap('js', function(name ) { // 回调函数 15 | console.log('js watched', name) 16 | }); 17 | } 18 | start() { 19 | this.hooks.arch.call('xiehang') 20 | } 21 | } 22 | 23 | 24 | let l = new Lession() 25 | l.tap(); // 注册了其中的事件 26 | l.start(); // 启动钩子 27 | 28 | // 原理就是,当调用实例方法tap()时,这个syncHook会将这其中注册的事件注册在一个数组中 29 | // 在调用start事件时,会将这两个事件依次执行 30 | // this.hooks.arch.call('xiehang'),这个call即是执行事件,并传递一个参数进去 -------------------------------------------------------------------------------- /lib/async.start.1.js: -------------------------------------------------------------------------------- 1 | class SyncHook{ // 定义一个同步钩子 2 | constructor(args) { // args即是实例化传递进来的参数,即 args => ['name'] 3 | this.tasks = [] 4 | } 5 | tap(name, task) { 6 | this.tasks.push(task) 7 | } 8 | call(...args) { 9 | this.tasks.forEach((task) => task(...args)) 10 | } 11 | } 12 | 13 | 14 | 15 | 16 | let hook = new SyncHook(['name']) 17 | 18 | hook.tap('node', function(name) { 19 | console.log('node', name) 20 | }) 21 | hook.tap('js', function(name) { 22 | console.log('js', name) 23 | }) 24 | 25 | hook.call('xiehang') 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lib/asyncParalleHook.case.js: -------------------------------------------------------------------------------- 1 | let { AsyncParallelHook } = require('tapable') 2 | 3 | class Lession{ 4 | constructor() { 5 | this.hooks = { // 订阅一些钩子,然后用lession的实例来启动这些钩子 6 | arch: new AsyncParallelHook(['name']), // 定义了一个钩子,可以订任意个 7 | } 8 | } 9 | tap() { // 注册监听函数 10 | // 钩子上有一个tapAsync注册事件,异步注册事件 11 | // this.hooks.arch.tapAsync('node', function(name, cb ) { // 回调函数 12 | // setTimeout(() => { 13 | // console.log('node watched', name) 14 | // cb() 15 | // }, 1000) 16 | // }); 17 | // this.hooks.arch.tapAsync('js', function(name, cb ) { // 回调函数 18 | // setTimeout(() => { 19 | // console.log('js watched', name) 20 | // cb() 21 | // }, 1000) 22 | // }); 23 | /********************tapPromise注册事件*************************/ 24 | this.hooks.arch.tapPromise('node', function(name){ 25 | return new Promise(function(resolve, reject){ 26 | setTimeout(() => { 27 | console.log('node', name) 28 | resolve() 29 | }, 1000) 30 | }) 31 | }) 32 | this.hooks.arch.tapPromise('js', function(name){ 33 | return new Promise(function(resolve, reject){ 34 | setTimeout(() => { 35 | console.log('js', name) 36 | resolve() 37 | }, 1000) 38 | }) 39 | }) 40 | /*************************************************************/ 41 | } 42 | start() { 43 | // this.hooks.arch.callAsync('xiehang', function() { 44 | // console.log('all of events registered has been executed') 45 | // }) 46 | /*********************tapPromise执行事件***********************/ 47 | this.hooks.arch.promise('xiehang').then(() => { 48 | console.log('all of events registered has been executed') 49 | }) 50 | /*************************************************************/ 51 | } 52 | } 53 | 54 | 55 | let l = new Lession() 56 | l.tap(); // 注册了其中的事件 57 | l.start(); // 启动钩子 -------------------------------------------------------------------------------- /lib/asyncParalleHook.start.js: -------------------------------------------------------------------------------- 1 | 2 | class asyncParalleHook{ 3 | constructor(args) { 4 | this.tasks = [] 5 | } 6 | tapAsync(name, task) { 7 | this.tasks.push(task) 8 | } 9 | callAsync(...args) { 10 | let fianlCallback = args.pop(); 11 | let index = 0; 12 | let done = () => { 13 | index++; 14 | if(index === this.tasks.length) fianlCallback() 15 | } 16 | this.tasks.forEach(task => { 17 | task(...args, done) 18 | }) 19 | } 20 | } 21 | 22 | 23 | let hook = new asyncParalleHook(['name']); 24 | let total = 0; 25 | hook.tapAsync('webpack', function(name, cb) { 26 | setTimeout(() => { 27 | console.log('webpack', name) 28 | cb() 29 | }, 1000) 30 | }) 31 | 32 | hook.tapAsync('node', function(name, cb) { 33 | setTimeout(() => { 34 | console.log('node', name) 35 | cb() 36 | }, 1000) 37 | }) 38 | 39 | hook.callAsync('xiehang', function() { 40 | console.log('当注册事件都执行完之后,才会执行这个回调。') 41 | console.log('all of events registered has been executed') 42 | }) 43 | 44 | 45 | -------------------------------------------------------------------------------- /lib/asyncSeriesHook.case.js: -------------------------------------------------------------------------------- 1 | let { AsyncSeriesHook } = require('tapable') 2 | 3 | class Lession{ 4 | constructor(){ 5 | this.index = 0 6 | this.hooks = { 7 | archs: new AsyncSeriesHook(['name']) 8 | } 9 | } 10 | tap(){ 11 | this.hooks.archs.tapAsync('node', function(name, cb){ 12 | return new Promise(function(resolve, reject){ 13 | setTimeout(() => { 14 | console.log('node', name) 15 | cb() 16 | }, 1000) 17 | }) 18 | }) 19 | this.hooks.archs.tapAsync('vue', function(name, cb){ 20 | return new Promise(function(resolve, reject){ 21 | setTimeout(() => { 22 | console.log('vue', name) 23 | cb() 24 | }, 1000) 25 | }) 26 | }) 27 | } 28 | start(){ 29 | this.hooks.archs.callAsync('xiehang', function() { 30 | console.log('callback function is been execute') 31 | }) 32 | } 33 | } 34 | 35 | 36 | let hook = new Lession() 37 | hook.tap() 38 | hook.start() 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /lib/asyncSeriesHook.start.js: -------------------------------------------------------------------------------- 1 | class asyncSeriesHook{ 2 | constructor(args){ 3 | this.tasks = [] 4 | } 5 | tapAsync(name, task){ 6 | this.tasks.push(task) 7 | } 8 | callAsync(...args){ 9 | console.log('...args', args) 10 | let finalCallback = args.pop() 11 | let index = 0 12 | let next = () => { 13 | if(this.tasks.length === index) return finalCallback() 14 | let task = this.tasks[index++]; 15 | task(...args, next) 16 | } 17 | next() 18 | } 19 | } 20 | 21 | let hook = new asyncSeriesHook(['name']) 22 | 23 | hook.tapAsync('webpack', function(name, cb){ 24 | setTimeout(() => { 25 | console.log('webpack', name) 26 | cb(name, 'webpack') 27 | }, 2000) 28 | }) 29 | hook.tapAsync('vue', function(name, cb){ 30 | setTimeout(() => { 31 | console.log('vue', name) 32 | cb() 33 | }, 1000) 34 | }) 35 | 36 | hook.callAsync('xiehang', function() { 37 | console.log('xiehang finshed all lessions' ) 38 | }) 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /lib/asyncSeriesWaterfallHook.case.js: -------------------------------------------------------------------------------- 1 | let { AsyncSeriesWaterfallHook } = require('tapable') 2 | 3 | class Lession{ 4 | constructor(){ 5 | this.index = 0 6 | this.hooks = { 7 | archs: new AsyncSeriesWaterfallHook(['name']) 8 | } 9 | } 10 | tap(){ 11 | this.hooks.archs.tapAsync('node', function(name, cb){ 12 | setTimeout(() => { 13 | console.log('node', name) 14 | // 第一个参数是null, 标示没有错误,第二个参数表示传给下一个函数的参数 15 | // 如果第一个参数不是null, 那么就中断后面的函数的执行,直接执行最后的回调函数 16 | cb(null, 'result'); 17 | }, 1000) 18 | }) 19 | this.hooks.archs.tapAsync('js', function(data, cb){ 20 | setTimeout(() => { 21 | console.log('js', data) 22 | cb(null, 'js') 23 | }, 1000) 24 | }) 25 | this.hooks.archs.tapAsync('vue', function(data, cb){ 26 | setTimeout(() => { 27 | console.log('vue', data) 28 | cb() 29 | }, 1000) 30 | }) 31 | } 32 | start(){ 33 | this.hooks.archs.callAsync('xiehang', function() { 34 | console.log('callback function is been execute') 35 | }) 36 | } 37 | } 38 | 39 | 40 | let hook = new Lession() 41 | hook.tap() 42 | hook.start() 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /lib/asyncSeriesWaterfallHook.start.js: -------------------------------------------------------------------------------- 1 | class asyncSeriesWaterfallHook{ 2 | constructor(args){ 3 | this.tasks = [] 4 | } 5 | tapAsync(name, task){ 6 | this.tasks.push(task) 7 | } 8 | callAsync(...args){ 9 | let index = 0; 10 | let finalCallback = args.pop(); 11 | let next = (err, data) => { 12 | let task = this.tasks[index]; 13 | if(!task || err) return finalCallback(); 14 | if(index === 0) { 15 | task(...args, next) 16 | } else { 17 | task(data, next) 18 | } 19 | index++ 20 | } 21 | next() 22 | } 23 | } 24 | 25 | 26 | 27 | let hook = new asyncSeriesWaterfallHook(['name']) 28 | 29 | hook.tapAsync('webpack', function(name, cb){ 30 | setTimeout(() => { 31 | console.log('webpack', name) 32 | cb(null, name) 33 | }, 1000) 34 | }) 35 | hook.tapAsync('vue', function(data, cb){ 36 | setTimeout(() => { 37 | console.log('vue', data) 38 | cb(null, data) 39 | }, 1000) 40 | }) 41 | 42 | hook.callAsync('xiehang', function() { 43 | console.log('xiehang finshed all lessions' ) 44 | }) 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /lib/syncBailHook.js: -------------------------------------------------------------------------------- 1 | class SyncBailHook{ // 定义一个同步钩子 2 | constructor(args) { // args即是实例化传递进来的参数,即 args => ['name'] 3 | this.tasks = [] 4 | } 5 | tap(name, task) { 6 | this.tasks.push(task) 7 | } 8 | call(...args) { 9 | // this.tasks.forEach((task) => task(...args)) 10 | let ret; // 当前函数的返回值 11 | let index = 0; // 当前函数的索引 12 | do{ 13 | ret = this.tasks[index++](...args) 14 | } while(ret === undefined && index < this.tasks.length) 15 | } 16 | } 17 | 18 | 19 | 20 | 21 | let hook = new SyncBailHook(['name']) 22 | 23 | hook.tap('node', function(name) { 24 | console.log('node', name) 25 | return 'stop' 26 | }) 27 | hook.tap('js', function(name) { 28 | console.log('js', name) 29 | }) 30 | 31 | hook.call('xiehang') -------------------------------------------------------------------------------- /lib/syncLoopHook.js: -------------------------------------------------------------------------------- 1 | 2 | // 同步函数遇到某个不返回undefined的函数,会多次执行 3 | class SyncLoopHook{ // 定义一个同步钩子 4 | constructor(args) { // args即是实例化传递进来的参数,即 args => ['name'] 5 | this.tasks = [] 6 | } 7 | tap(name, task) { 8 | this.tasks.push(task) 9 | } 10 | call(...args) { 11 | this.tasks.forEach(task => { 12 | let ret; 13 | do{ 14 | ret = task(...args) 15 | } while(ret !== undefined) 16 | }) 17 | } 18 | } 19 | 20 | 21 | 22 | 23 | let hook = new SyncLoopHook(['name']) 24 | let total = 0 25 | hook.tap('node', function(name) { 26 | console.log('node', name) 27 | return ++total === 3 ? undefined : 'do 3 times' 28 | }) 29 | hook.tap('js', function(name) { 30 | console.log('js', name) 31 | }) 32 | hook.tap('css', function(name) { 33 | console.log('css', name) 34 | }) 35 | hook.call('xiehang') 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/syncWaterfallHook.js: -------------------------------------------------------------------------------- 1 | 2 | // 将上一个函数的返回值传给下一个函数 3 | class SyncWaterfallHook{ // 定义一个同步钩子 4 | constructor(args) { // args即是实例化传递进来的参数,即 args => ['name'] 5 | this.tasks = [] 6 | } 7 | tap(name, task) { 8 | this.tasks.push(task) 9 | } 10 | call(...args) { 11 | let [ first, ...others] = this.tasks 12 | let ret = first(...args); 13 | others.reduce((a,b) => { 14 | console.log('a', a) 15 | return b(a) 16 | }, ret) 17 | } 18 | } 19 | 20 | 21 | 22 | 23 | let hook = new SyncWaterfallHook(['name']) 24 | 25 | hook.tap('node', function(name) { 26 | console.log('node', name) 27 | return 'node' 28 | }) 29 | hook.tap('js', function(name) { 30 | console.log('js', name) 31 | return 'js' 32 | }) 33 | hook.tap('css', function(name) { 34 | console.log('css', name) 35 | }) 36 | hook.call('xiehang') 37 | 38 | 39 | -------------------------------------------------------------------------------- /multiEntry.md: -------------------------------------------------------------------------------- 1 | ## 多入口页面的配置 2 | ``` 3 | let path = require('path') 4 | let htmlWebpackPlugin = require('html-webpack-plugin') // 这个插件的作用就是用模版产生html,并自动把js引进去 5 | module.exports = { 6 | mode: 'development', 7 | entry: { 8 | index: './src/index.js', 9 | a: './src/a.js', 10 | }, 11 | output: { 12 | filename: '[name].js', // [name]表示入口模块的名字 13 | path: path.resolve(__dirname, 'dist'), 14 | }, 15 | plugins: [ 16 | new htmlWebpackPlugin({ 17 | template: './src/index.html', // 选择那个文件作为html的模版文件 18 | filename: 'index.html', // 如果有两个入口,就要new两次这个插件 19 | // 如果这个html中两个js文件都需要,那么就在chunks中添加两个文件 20 | chunks: ['index', 'a'], 21 | }), 22 | new htmlWebpackPlugin({ 23 | template: './src/index.html', // 选择那个文件作为html的模版文件 24 | filename: 'a.html', // 如果有两个入口,就要new两次这个插件 25 | // 这样配置,它会默认把index.js 和 a.js这两个文件都引入这两个html文件中 26 | // 期望index.html引入index.js a.html引入a.js 27 | // 可以配置chunks 28 | chunks: ['index'], 29 | }), 30 | ], 31 | } 32 | ``` 33 | 34 | ## 在打包文件中产生映射文件,方便我们调试 35 | - devtool: 'source-map', // 增加映射文件,帮助我们调试源代码,会单独生成一个源码文件,而且出错的代码会被标记出来 36 | - 如果配置eval-source-map, 这样就不会生成一个map文件,但是也会将出错的代码标记出来 37 | - 配置成cheap-module-source-map,这样不会产生列,但是是一个单独的映射文件,产生后你可以保存起来 38 | - 配置成cheap-module-eval-source-map,不会产生新的文件,而是集成在打包的文件中 39 | 40 | 41 | ## 实时打包代码 42 | - webpack-dev-server是实时运行改变后的代码,并不能生成打包的代码 43 | - 在配置中设置watch: true,这样webpack就会监听代码,只要改变了代码,就会实时打包 44 | - watch也有配置选项,即配置 watchOptions 45 | ``` 46 | watchOptions: { // 监听的选项 47 | poll: 1000, // 每秒问多少次,是否需要更新 48 | ignored: /node_modules/, // 忽略监听哪个文件夹的文件 49 | aggregateTimeout: 500, // 在保存文件之后500ms打包,即防抖 和 节流操作 50 | } 51 | ``` 52 | 53 | ## webpack解决跨域的问题 54 | - 创建一个服务文件server.js启动一个服务(vscode中安装code runner插件右键点击Run Code即可运行这个单文件) 55 | - 在入口页面中建立一个ajax异步请求任务 56 | - 在webpack.config.js中配置本地服务的参数devServer 57 | ``` 58 | devServer: { 59 | proxy: { // 做请求代理解决跨域的问题 60 | '/api': { 61 | target: 'http://127.0.0.1:3000', // 配置一个代理地址 62 | pathRewrite: { '/api': '' }, // 后端接口地址一般不会加api,前端将地址中的api进行重写 63 | } 64 | }, 65 | } 66 | ``` 67 | ------ 68 | - 有时候我们不需要做代理,我们只需要前端mock一些假数据。 69 | - webpack-dev-server内部就是用express模块的,我们可以直接在配置里面写接口。直接在devServer中写一个方法before,这是webpack-dev-server提供的方法,相当于钩子函数 70 | ``` 71 | devServer: { 72 | before(app) { // 这个app就相当于在server.js中使用express创建的express实例app 73 | app.get('/user', (req, res) => { 74 | res.json({ name: 'webpack跨域的问题' }) 75 | }) 76 | }, 77 | } 78 | ``` 79 | ------ 80 | - 第三种:有服务端,但是不想用代理来处理。能不能在服务端中启动webpack,端口用服务端端口,相当于在服务端启动页面,前端和服务端启动在一个端口上,这样也不会有跨域问题 81 | 82 | - 这里就不是运行webpack,而是运行服务端了,在服务端引入webpack,并且需要使用webpack的中间件 webpack-dev-middleware 83 | - npm i webpack-dev-middleware -d 84 | - 在server.js中增加代码 85 | ``` 86 | // 使用服务端启动页面,引入webpack和中间件 87 | let webpack = require('webpack'); 88 | let middle = require('webpack-dev-middleware'); 89 | 90 | let config = require('./webpack.config') 91 | let compiler = webpack(config) 92 | app.use(middle(compiler)) 93 | ``` 94 | - 启动server文件,在页面访问localhost:3000,就能拿到页面。 95 | - 这就是后端和前端启动在一起。 96 | - 以上就是前端解决跨域的三种方式 97 | 98 | 99 | ## resolve配置 100 | - 在代码中我们会引用一些模块,有些模块是从第三方模块中引入 101 | - 在common.js规范中,先查找当前目录下的node_modules,如果找不到,就往上一层找 102 | - 我们可以配置,强制在哪个文件夹中查找 103 | ``` 104 | resolve: { 105 | modules: [path.resolve('node_modules'), path.resolve('src/js')], // 只在这两个文件夹中查找 106 | } 107 | ``` 108 | - 我们在页面中导入样式 import ‘bootstrap’; 109 | - 在导入这个模块的时候,webpack会先去node_modules查找这个模块,他会默认去获取bootstrap -> package.json(main)所配置的文件,即dist/js/bootstrap文件,他是把js文件拿过来了, 110 | - 例: 111 | ``` 112 | // 在html中添加一个button,并设置danger类 113 | // 在index.js中引入 bootstrap, import ‘bootstrap’ 114 | // 这个样式是不会生效的 115 | // 解决办法可以是引入这个css文件,即:import 'bootstrap/dist/css/bootstrap.css'; 这样就能看到bootstrap的danger类按钮样式 116 | // 还可以给这个文件配置一个别名 117 | resolve: { 118 | alias: { // 配置别名 119 | bootstrap: 'bootstrap/dist/css/bootstrap.css', 120 | }, 121 | } 122 | // 还可以设置查找模块的主入口 123 | resolve: { 124 | mainFields: ['style', 'main'], // 这样就会先找对应模块中package.json中配置的style所对应的文件,再找main所对应的文件 125 | } 126 | // 还可以配置文件扩展名 127 | resolve: { 128 | extensions: ['css', 'js', 'json'], // 这样导入 import './index'时,会先查找index.css,找不到就找index.js、index.json 129 | } 130 | ``` 131 | 132 | ## 定义环境变量 133 | ``` 134 | plugins: [ 135 | new webpack.DefinePlugin({ 136 | DEV: "'dev'", // 定义环境变量,注:只写一个引号,会报错,在页面拿DEV时,提示dev未定义,页面将dev当作变量了 137 | // 或者写成 DEV: JSON.stringify('dev') 138 | // webpack是将单引号中的当作一个语句或变量 139 | // flag: 'true', // 页面拿flag就是一个boolean值 extenssion: '1+1', // 页面就拿到1+1这个表达式的结果2 140 | }) 141 | ] 142 | ``` 143 | 144 | ## 开发环境和生产环境不同配置 145 | - 在开发环境和生产环境的配置肯定不同,如果每次都更改配置和模式,就显得太麻烦,我们可以将开发环境和生产环境的配置文件分开,这样就显得方便很多 146 | - 安装webpack-merge插件 npm i webpack-merge -d 147 | - 增加webpack.dev.js webpack.prod.js,同时将之前的webpack.config.js改名成webpack.base.js 148 | - 在新增加的文件中引入基本配置,同时设置模式 和 各自的配置,如: 149 | ``` 150 | let { smart } = require('webpack-merge') 151 | 152 | let webpackBaseConfig = require('./webpack.base.js') 153 | 154 | module.exports = smart(webpackBaseConfig, { 155 | mode: 'production', 156 | }) 157 | ``` 158 | - 改变package.json中配置 159 | 1. "dev": "webpack-dev-server --config webpack.dev.js", 160 | 2. "build": "webpack --config webpack.prod.js", 161 | - 这样我们就能把开发环境的如:devServer、devtool等单独拿出来配置,生产环境中的 optimization: { minimizer: [] }提取出来 162 | 163 | ## 忽略掉不需要的插件 164 | - 在引用某个第三方库的时候,可能有时候不需要包中的某些依赖,比如:在moment这个插件中,它包含了很多种语言包,在使用moment时,会将所有的语言包都导入进来,这样就导致最后打的包很大且不必要。moment中引入的语言包是 /node_modules/moment/locale/ 165 | - 我们就可以配置webpack.IgnorePlugin(/\.\/locale/, /momemt/) 166 | ``` 167 | plugins: [ 168 | new webpack.IgnorePlugin(/\.\/locale/, /momemt/), // 在引入moment插件时,忽略掉locale这个包 169 | ], 170 | // 在需要使用某个包时,手动引入这个包 171 | import moment from 'moment' 172 | 173 | import 'moment/locale/zh-cn' 174 | 175 | const r = moment().endof('day').fromNow() 176 | console.log(r) 177 | 178 | ``` 179 | ## 视频22、23忽略 180 | 181 | ## webpack在生产模式下会将无用的代码省略 182 | ``` 183 | // a.js 184 | let sum = (a, b) => { 185 | return a + b 186 | } 187 | let minus = (a, b) => { 188 | return a - b 189 | } 190 | export default { 191 | sum, minus 192 | } 193 | 194 | // index.js 195 | import calc from './a.js' 196 | console.log(calc.sum(1,2)) 197 | 198 | // 在生产环境中,通过es6方式(export/import)引入的,会将没有用到的代码不打进包中,自动去除没用的代码(tree-shaking) 199 | // 通过require引进来的是calc.default.sum,也就是说,es6引进来的是自动挂在到default上 200 | // 并且通过require引进来的,不会将没用的代码去除掉,这也就是前端为什么使用import语法 201 | 202 | ``` 203 | -------------------------------------------------------------------------------- /pack.md: -------------------------------------------------------------------------------- 1 | ## 解析webpack打包后的文件代码 2 | 3 | - 打包后的代码结构 4 | 5 | ``` 6 | (fucntion(modules){ 7 | // 先定义一个缓存 如果模块加载完了,不需要再次加载模块,就直接在缓存中去拿 8 | var installedModules = {}; 9 | // webpack中require方法的实现 10 | function __webpack_require__(moduleId) { 11 | 12 | // Check if module is in cache 13 | // 查看模块是否存在缓存中,如果存在就return 14 | if(installedModules[moduleId]) { 15 | return installedModules[moduleId].exports; 16 | } 17 | // Create a new module (and put it into the cache) 18 | // 如果模块不存在,就新定义一个模块名 19 | // 每一个模块就是key-value的一个对象,key就是模块的路径,value是一个对象(moduleId, 状态,exports对象) 20 | var module = installedModules[moduleId] = { 21 | i: moduleId, // 模块id 22 | l: false, // 是否加载完成 23 | exports: {} 24 | }; 25 | // Execute the module function 26 | // 在传进来的对象中找到当前模块并call执行 27 | modules[moduleId].call(module.exports(this指向), module, module.exports(模块空对象), __webpack_require__(require实现方法)); 28 | 29 | // Flag the module as loaded 30 | // 模块加载成功 31 | module.l = true; 32 | 33 | // Return the exports of the module 34 | return module.exports; 35 | } 36 | })({ 37 | "./src/a.js": (function(module, exports){ 38 | eval("function add(a, b) { return a + b } module.exports = {add : add}//# sourceURL=webpack:///./src/a.js?") 39 | }), 40 | "./src/index.js": (function(module, exports){ 41 | eval("const pluginA = __webpack_require__(\"./src/a.js\")console.log('this is my first webpack prictise')console.log('1 plus 2 equal', pluginA.add(1,2))//# sourceURL=webpack:///./src/index.js?"); 42 | 43 | }) 44 | }) 45 | 46 | ``` 47 | - 将所有模块作为一个对象传进匿名自执行函数中,对象中的key是模块的路径,value是匿名函数体 -------------------------------------------------------------------------------- /packPicture.md: -------------------------------------------------------------------------------- 1 | 2 | ## 如何在webpack中使用图片,并且打包图片 3 | - 图片的使用 4 | 1. 在js中创建图片来引入;import logo from './logo.png' 5 | 2. 在css中通过background引入 6 | 3. 在img标签中使用 7 | 8 | - 使用第一种的方式,webpack就要报错,需要file-loader,file-loader会默认在内部生成一张图片到dist目录下,并且把生成的图片的名字返回回来 9 | ``` 10 | npm i file-loader -d 11 | --- 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.(png|jpg|gif)$/, 16 | use: 'file-loader', 17 | } 18 | ] 19 | } 20 | --- 21 | 使用 22 | import logo from './logo.png' 23 | let image = new Image() 24 | image.src = logo 25 | document.body.appenChild(image) 26 | 27 | ``` 28 | - 在css中使用background添加图片,在css-loader中已经做了处理,默认就能使用。如background: url('./logo.png'); 在css-loader中做的处理是background: url(require('./logo.png')) 29 | 30 | - 在img标签中引入图片,需要用到html-withimg-loader, 它专门处理标签中的图片的引入 31 | ``` 32 | module: { 33 | rules: [ 34 | { 35 | test: /\.html$/, 36 | use: 'html-withimg-loader', 37 | } 38 | ] 39 | } 40 | ``` 41 | - 在实际情况中我们不会直接用file-loader,页面上用的图片非常小,我们不希望发送http请求,我们希望把图片变成base64,我们就需要用到url-loader。 42 | - 这个loader,我们可以做一个限制,当图片小于多少KB,我们就将图片转换成base64,否则用file-loader来产生真实的图片 43 | ``` 44 | module: { 45 | rules: [ 46 | { 47 | test: /\.(png|jpg|gif)$/, 48 | use: 'url-loader', 49 | options: { 50 | limit: 200*1024, // 小于200kb,就转换成base64 51 | }, 52 | } 53 | ] 54 | } 55 | ``` 56 | 57 | ## 给打包出来的文件进行分类,如:将所有的图片资源放在一个文件夹,将所有的js和css分别放在另外的文件夹 58 | ``` 59 | plugin: [ 60 | new miniCssExtractPlugin({ 61 | filename: 'css/index.css', // 将样式文件打包到css文件夹下 62 | }), 63 | ], 64 | module: { 65 | rules: [ 66 | { 67 | test: /\.(png|jpg|gif)$/, 68 | use: 'url-loader', 69 | options: { 70 | limit: 200*1024, // 小于200kb,就转换成base64 71 | outputPath: '/images/', // 将图片都存放在images文件夹下 72 | }, 73 | } 74 | ] 75 | }, 76 | ``` 77 | - 在最后运行,我们的文件都会在服务器上,我们在引用的时候希望能加上域名,即域名下的某个文件夹下的文件 78 | - 此时,我们可以配置publicPath: '/' 79 | ``` 80 | output: { 81 | filename: 'main.js', // 打包后的文件名,可以在文件中加上hash值,并规定hash值只有八位,main.[hash:8].js 82 | path: path.resolve(__dirname,'dist'), // 路径必须是一个绝对路径。加载path模块,这是webpack内置模块 83 | // __dirname表示当前目录,也可以不加 84 | publicPath: '/', // 这样在引用公共资源的时候,统一加上这个路径 85 | }, 86 | // 也可以只在图片的loader中加上publicPath: '/', 这样其他的资源就不会加上这个域名了 87 | rules: [ 88 | { 89 | test: /\.(png|jpg|gif)$/, 90 | use: 'url-loader', 91 | options: { 92 | limit: 200*1024, // 小于200kb,就转换成base64 93 | outputPath: '/images/', // 将图片都存放在images文件夹下 94 | publicPath: '/', 95 | }, 96 | } 97 | ] 98 | ``` 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-basic", 3 | "version": "1.0.0", 4 | "description": "learn webpack", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "webpack-dev-server --config webpack.dev.js", 9 | "build": "webpack --config webpack.prod.js", 10 | "named": "webpack --config webpack.config.my.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/typhoonIscoming/webpack-basic.git" 15 | }, 16 | "keywords": [ 17 | "webpack" 18 | ], 19 | "author": "xie", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/typhoonIscoming/webpack-basic/issues" 23 | }, 24 | "homepage": "https://github.com/typhoonIscoming/webpack-basic#readme", 25 | "devDependencies": { 26 | "@babel/plugin-transform-runtime": "^7.2.0", 27 | "file-loader": "^6.2.0", 28 | "url-loader": "^4.1.1", 29 | "webpack": "^4.29.0", 30 | "webpack-cli": "^3.2.1", 31 | "webpack-dev-server": "^3.1.14" 32 | }, 33 | "dependencies": { 34 | "@babel/core": "^7.2.2", 35 | "@babel/plugin-proposal-class-properties": "^7.3.0", 36 | "@babel/plugin-proposal-decorators": "^7.3.0", 37 | "@babel/plugin-syntax-dynamic-import": "^7.2.0", 38 | "@babel/polyfill": "^7.2.5", 39 | "@babel/preset-env": "^7.3.1", 40 | "@babel/runtime": "^7.3.1", 41 | "autoprefixer": "^9.4.7", 42 | "babel-loader": "^8.0.5", 43 | "babel-runtime": "^6.26.0", 44 | "bootstrap": "^4.3.1", 45 | "clean-webpack-plugin": "^1.0.1", 46 | "css-loader": "^2.1.0", 47 | "eslint": "^5.13.0", 48 | "eslint-loader": "^2.1.2", 49 | "expose-loader": "^0.7.5", 50 | "express": "^4.16.4", 51 | "html-webpack-plugin": "^3.2.0", 52 | "jquery": "^3.3.1", 53 | "less": "^3.9.0", 54 | "less-loader": "^4.1.0", 55 | "mini-css-extract-plugin": "^0.5.0", 56 | "moment": "^2.24.0", 57 | "optimize-css-assets-webpack-plugin": "^5.0.1", 58 | "postcss-loader": "^3.0.0", 59 | "style-loader": "^0.23.1", 60 | "tapable": "^1.1.1", 61 | "uglifyjs-webpack-plugin": "^2.1.1", 62 | "webpack-dev-middleware": "^3.5.2", 63 | "webpack-merge": "^4.2.1" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | plugins: [require('autoprefixer')] 4 | } -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 2 | let express = require('express'); 3 | 4 | let app = express() 5 | 6 | 7 | // 使用服务端启动页面,引入webpack和中间件 8 | let webpack = require('webpack'); 9 | let middle = require('webpack-dev-middleware'); 10 | 11 | let config = require('./webpack.base') 12 | let compiler = webpack(config) 13 | app.use(middle(compiler)) 14 | 15 | 16 | 17 | // // 这样写的话,默认是访问http://localhost:8080/api/user 18 | // // 请求这个地址是webpack-dev-server通过express模块创建的服务,我们可以再通过服务转发到端口是3000的地址 19 | app.get('/user', (req, res) => { 20 | res.json({ name: 'webpack跨域的问题' }) 21 | }) 22 | 23 | 24 | 25 | 26 | app.listen(3000, 'localhost', function(){ 27 | const host = this.address().address 28 | const port = this.address().port 29 | console.log('访问地址为 http://%s:%s', host, port) 30 | }) 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/a.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | require('@babel/polyfill') 4 | 5 | 6 | function add(a, b) { 7 | return a + b 8 | } 9 | 10 | // class B{ 11 | // b = 20; 12 | // } 13 | 14 | // function * gen(){ 15 | // yield 1; 16 | // return 2 17 | // } 18 | // let g = new gen(); 19 | // const a = g.next(); 20 | // const b = g.next(3) 21 | // console.log('this is genaretor function', a, b) 22 | 23 | 24 | 25 | 26 | module.exports = { 27 | add: add, 28 | } -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | *{ padding: 0; margin: 0; } 2 | html, body{ 3 | width: 100%; height: 100%; 4 | } -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | webpack prictise 8 | 10 | 11 | 12 |
13 | hdh 14 |

btn

15 |
16 | 17 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var tool = require('./a') 4 | require('./index.css') 5 | require('./index.less') 6 | 7 | import moment from 'moment' 8 | 9 | 10 | import 'bootstrap/dist/css/bootstrap.css' 11 | // import $ from 'jquery'; // 将jquery暴露成全局变量$ 12 | console.log('实时打包') 13 | console.log('print environment var xixixixix', DEV) 14 | let fn = () => { 15 | console.log('this is es6 syntax') 16 | } 17 | fn() 18 | 19 | 20 | import 'moment/locale/zh-cn' 21 | 22 | const r = moment().endOf('day').fromNow() 23 | console.log('print dateTime', r) 24 | 25 | // @log 26 | // class A { 27 | // a = 12; 28 | // } 29 | // let a = new A() 30 | 31 | // function log(target){ 32 | // console.log('类的装饰器class decorators', target) 33 | // } 34 | // console.log('this is class A', a.a) 35 | console.log('this is my first webpack prictise') 36 | console.log('9 plus 2 equal', tool.add(9,2)) 37 | 38 | // console.log($, window.$) 39 | 40 | // let xhr = new XMLHttpRequest(); 41 | 42 | // xhr.open('GET', '/user', true) 43 | 44 | // xhr.onload = function(res) { 45 | // console.log(xhr.response) 46 | // const { response } = res.target 47 | // if(res.target.status === 200) { 48 | // const html = document.createElement('p') 49 | // html.innerText = response 50 | // document.getElementsByClassName('content')[0].append(html) 51 | // } 52 | // } 53 | // xhr.send(); 54 | 55 | // const btn = document.getElementsByClassName('btn')[0] 56 | 57 | // btn.addEventListener('click', function(){ 58 | // import('./source.js').then((res) => { 59 | // // 它会将导入的结果放在default上,res.default即导入的结果 60 | // console.log('print lazy load result = ', res.default) 61 | // }) 62 | // }) 63 | import source from './source' 64 | if(module.hot) { // 如果模块支持热更新 65 | module.hot.accept('./source', () => { 66 | let source = require('./source') 67 | console.log('file is been reload', source) 68 | }) 69 | } 70 | 71 | 72 | let obj = { 73 | a: { 74 | value: 10, 75 | b: { 76 | total: 11, 77 | } 78 | }, 79 | c: { 80 | value: 10, 81 | b: { 82 | total: 11, 83 | } 84 | }, 85 | e: { 86 | value: 10, 87 | b: { 88 | total: 11, 89 | } 90 | }, 91 | } 92 | 93 | let newObj = [] 94 | for(let key in obj){ 95 | newObj.push({ code: key, newvalue: obj[key].value || '', newTotal: obj[key].b.total || '' }) 96 | } 97 | console.log('newobj', newObj) 98 | -------------------------------------------------------------------------------- /src/index.less: -------------------------------------------------------------------------------- 1 | 2 | body{ 3 | #app{ 4 | height: 100%; 5 | background: url('./tianfuluo.png')no-repeat; 6 | background-size: contain; 7 | p{ 8 | text-align: center; 9 | } 10 | .routate-box{ 11 | width: 20px; 12 | height: 30px; 13 | transform: translateX(100px) rotate(10deg); 14 | border: 1px solid blue; 15 | } 16 | } 17 | .content{ 18 | .plus-content{ 19 | color: yellowgreen; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/source.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const print = 'this is lazy load 1112225' 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | export default print -------------------------------------------------------------------------------- /src/tianfuluo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typhoonIscoming/webpack-basic/ddd862ff4d323f497748991d27ec9958b92914f2/src/tianfuluo.png -------------------------------------------------------------------------------- /webpack.base.js: -------------------------------------------------------------------------------- 1 | // webpack 是node写出来的 需要node的写法 2 | 3 | const path = require('path') 4 | const HTMLWebpackPlugin = require('html-webpack-plugin') 5 | const miniCssExtractPlugin = require('mini-css-extract-plugin') 6 | let OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin') 7 | let UglifyJsPlugin = require('uglifyjs-webpack-plugin') 8 | const cleanPlugin = require('clean-webpack-plugin') 9 | const webpack = require('webpack') 10 | 11 | module.exports = { 12 | mode: 'development', // 模式 默认两种模式( production, development ) 13 | entry: './src/index.js', // 入口 相对路径 14 | output: { 15 | filename: 'main.js', // 打包后的文件名,可以在文件中加上hash值,并规定hash值只有八位,main.[hash:8].js 16 | path: path.resolve(__dirname,'dist'), // 路径必须是一个绝对路径。加载path模块,这是webpack内置模块 17 | // __dirname表示当前目录,也可以不加 18 | }, 19 | devServer: { // 开发服务器的配置 20 | port: 8000, 21 | progress: true, // 进度条 22 | contentBase: './dist', // 将这个目录作为静态服务器目录 23 | compress: true, 24 | hot: true, // 启用热更新 25 | proxy: { // 做请求代理解决跨域的问题 26 | // '/api': { 27 | // target: 'http://127.0.0.1:3000', // 配置一个代理地址 28 | // pathRewrite: { '/api': '' }, 29 | // }, 30 | }, 31 | // before(app) { // 这个app就相当于在server.js中使用express创建的express实例app 32 | // app.get('/user', (req, res) => { 33 | // res.json({ name: 'webpack跨域的问题' }) 34 | // }) 35 | // }, 36 | }, 37 | devtool: 'cheap-module-eval-source-map', // 配置source-map,会增加映射文件,帮助我们调试源代码,会单独生成一个源码文件,而且出错的代码会被标记出来 38 | // 如果配置eval-source-map, 这样就不会生成一个map文件,但是也会将出错的代码标记出来 39 | // 配置成cheap-module-source-map,这样不会产生列,但是是一个单独的映射文件,产生后你可以保存起来 40 | // 配置成cheap-module-eval-source-map,不会产生新的文件,而是集成在打包的文件中,不会产生报错列标示 41 | optimization: { // webpack4提供的一个优化项 42 | minimizer: [ 43 | new UglifyJsPlugin({ 44 | cache: true, 45 | parallel: true, // 是否是并发打包的,可以一起压缩多个文件 46 | sourceMap: true // set to true if you want JS source maps 47 | }), 48 | new OptimizeCSSAssetsPlugin({}), // 添加了这个配置,就必须添加UglifyJsPlugin,否则js就不会被压缩 49 | ], 50 | }, 51 | watch: true, 52 | watchOptions: { 53 | poll: 1000, // 每秒问多少次,是否需要更新 54 | ignored: /node_modules/, // 忽略监听哪个文件夹的文件 55 | aggregateTimeout: 500, 56 | }, 57 | plugins: [ // 存放所有webpack插件 58 | new HTMLWebpackPlugin({ 59 | template: './src/index.html', // 以这个文件作为html的模版 60 | filename: 'index.html', 61 | minify: { 62 | removeAttributeQuotes: true, 63 | // collapseWhitespace: true, 64 | }, 65 | hash: true, // 给生成的文件加上hash戳 66 | }), 67 | new miniCssExtractPlugin({ 68 | filename: 'index.css', 69 | }), 70 | new cleanPlugin([ 71 | './dist', //参数是一个数组,数组中是需要删除的目录名 72 | ]), 73 | new webpack.ProvidePlugin({ // 在每个模块中都注入$ 74 | $: 'jquery', 75 | }), 76 | new webpack.DefinePlugin({ 77 | DEV: "'dev'" 78 | }), 79 | new webpack.NamedModulesPlugin(), // 可以打印更新的模块的名字 80 | new webpack.HotModuleReplacementPlugin(), // 热更新插件 81 | ], 82 | module: { 83 | noParse: /jquery/, // 不去解析jquery中的依赖关系 如果你确定这个第三方包中没有其他的依赖,那么就可以设置不去解析这个包 84 | rules: [ 85 | // { 86 | // test: require.resolve('jquery'), 87 | // use: 'expose-loader?$', 88 | // }, 89 | // { 90 | // test: /\.js$/, 91 | // use: { 92 | // loader: 'eslint-loader', 93 | // options: { 94 | // enforce: 'pre', // previous 强制在前执行 post 强制在后执行 95 | // }, 96 | // }, 97 | // exclude: /node_modules/, 98 | // }, 99 | { 100 | test: /\.js$/, // 这是一个普通loader 101 | use: { 102 | loader: 'babel-loader', 103 | options: { // 用babel-loader 把es6转换成es5 104 | presets: ['@babel/preset-env'], // 这是一个大插件的集合,preset-env就是将es6转化成es5 105 | // 当然更高级的语法,还需要添加其他的插件 如类 class 106 | plugins: [ 107 | ["@babel/plugin-proposal-decorators", { "legacy": true }], // 类的装饰器 108 | ["@babel/plugin-proposal-class-properties", { "loose" : true }], // 解析类class 的插件 109 | [ 110 | '@babel/plugin-transform-runtime', 111 | { 112 | "corejs": false, 113 | "helpers": true, 114 | "regenerator": true, 115 | "useESModules": false 116 | }, 117 | ], 118 | '@babel/plugin-syntax-dynamic-import', 119 | // '@babel/polyfill', 120 | ] 121 | }, 122 | }, 123 | exclude: /node_modules/, 124 | include: path.resolve(__dirname, 'src'), // 只对src下的js进行转换,否则它会对node_modules中的js进行转换 125 | }, 126 | // 安装style-loader css-loader 127 | // 规则 css-loader 它主要是解析@import这种语法的,因为在css语法中有@import这种引入 解析路径 128 | // style-loader 它主要是把css插入到head标签中的 129 | // 为什么需要两个loader呢?因为loader的特点就是单一性 130 | // loader的用法 字符串(一个loader)‘css-loader’ 131 | // 多个loader时需要数组 132 | // loader的顺序默认是从右向左 133 | // 数组中的loader还可以写成对象的方式,好处就是可以传一个参数 134 | // 最后将css和less模块引入到入口文件即可生效 135 | { 136 | test: /\.css$/, 137 | use: [ 138 | // { 139 | // loader: 'style-loader', 140 | // options: { 141 | // insertAt: 'top', // 在插入样式时,将模块样式插入到head标签的顶部,这样, 142 | // //如果我们有样式写在head中,我们的样式就会覆盖相同层级的css模块中的样式 143 | // }, 144 | // }, 145 | miniCssExtractPlugin.loader, 146 | 'css-loader', 147 | 'postcss-loader', 148 | ] 149 | }, 150 | { test: /\.less$/, use: [ 151 | // { 152 | // loader: 'style-loader', 153 | // options: { 154 | // insertAt: 'top', // 在插入样式时,将模块样式插入到head标签的顶部,这样, 155 | // //如果我们有样式写在head中,我们的样式就会覆盖相同层级的css模块中的样式 156 | // }, 157 | // }, 158 | miniCssExtractPlugin.loader, 159 | 'css-loader', 160 | 'postcss-loader', 161 | 'less-loader', // 将less转换成css 162 | // 如果是使用的sass文件,则安装node-sass 和 sass-loader即可 163 | ] 164 | }, 165 | { 166 | test: /\.(png|jpg|gif)$/i, 167 | use: [ 168 | { 169 | loader: 'url-loader', 170 | options: { 171 | limit: 8192, 172 | mimetype: 'image/png', 173 | esModule: false, 174 | name: path.posix.join('static', 'img/[name].[hash:7].[ext]'), 175 | }, 176 | }, 177 | ], 178 | }, 179 | ], 180 | }, 181 | } -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | let { smart } = require('webpack-merge') 2 | let webpack = require('webpack') 3 | 4 | let webpackBaseConfig = require('./webpack.base.js') 5 | 6 | module.exports = smart(webpackBaseConfig, { 7 | mode: 'development', 8 | plugins: [ 9 | new webpack.IgnorePlugin(/\.\/locale/, /moment/), 10 | ], 11 | }) -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | let { smart } = require('webpack-merge') 2 | let webpack = require('webpack') 3 | 4 | let webpackBaseConfig = require('./webpack.base.js') 5 | 6 | module.exports = smart(webpackBaseConfig, { 7 | mode: 'production', 8 | plugins: [ 9 | new webpack.IgnorePlugin(/\.\/locale/, /moment/), 10 | ], 11 | watch: false, 12 | }) --------------------------------------------------------------------------------