Webpack学习笔记

本文共有19799个字,关键词:webpack

其实一开始是想着一步步看着官网英文教程demo和结合imooc的教程做下来,后来做了差不多第十节才发现居然有中文版的,风中凌乱。。。不过还是有选择性的记录一下整个学习的笔记,跟着都敲了一遍,详细看官网中文版

一、webpack的优点

  • 代码拆分
    webpack有两种组织代码的方式 一种是同步一种是异步。异步依赖作为分割点,形成一个新的块。在优化过了依赖树后,每一个异步区块都作为一个文件被打包。
  • loader
  1. 本身只处理原生的js,但是loader转化器可以将各种资源转换成为js模块。这样任何资源都可以成为 webpack的处理对象。比如处理css的cssloader,处理coffeescript coffeesecriptloader。
  • 智能解析
    webpack是一个智能解析器,可以处理几乎任何第三方的库,无论他们的模块形式是commonjs,amd 还是普通的js文件。甚至加载依赖的时候可以动态表达式

二、安装

npm init
# 全局安装
npm install webpack -g   
# 安装在项目中
npm install --save-dev

PS:Windows下建议还是全局安装吧,全局安装配置一次环境变量就可以使用。若选择安装在项目中,则每一个项目都需要配置一下环境变量,如D:\webpack-demo\node_modules\.bin

三、开始使用

  1. 开始一个demo文件夹
mkdir webpack-demo && cd webpack-demo
npm init -y
npm install --save-dev webpack
  1. 在项目中新建文件,src用于保存源文件,dist(distribution)用于保存发布的代码,即打包生成的代码。
  webpack-demo
  |- package.json
+ |- /dist
+   |- index.html
+ |- /src
+   |- index.js
  1. index.js
function compoent(){
    var ele=document.createElement('div');
    ele.innerHTML="Hello Webpack!"
    return ele;
}
document.body.appendChild(compoent());
  1. index.html
<html>
    <head>
      <title>Getting Started</title>
    </head>
    <body>
        <!--bundle.js是下面将要根据index.js打包生成的文件-->
        <script src="bundle.js"></script>
    </body>
  </html>
  1. 在项目根目录下,输入命令开始打包:webpack 源文件 目标文件,打包后可以在dist文件夹下看到自动生成了bundle.js。
webpack src/index.js dist/bundle.js
  1. 使用配置文件
  • 在根目录添加webpack.config.js,直接运行webpack(不需要带参数),即可根据配置打包
const path=require('path');
module.exports={
    entry:'./src/index.js',
    output:{
        path:path.resolve(__dirname, './dist'),
        filename:'bundle.js'
    }
}

PS:默认配置文件是webpack.config.js,修改默认的配置文件,用webpack --config webpack.dev.config.js

  • 把脚本写进package.json,通过npm run build命令运行,如
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build":"webpack"
  },
  1. 常用的参数
  • 自动打包,加上--watch
webpack src/hello.js dist/bundle.js --module-bind 'css=style-loader!css-loader' --watch
  • 看到打包过程,--progress
  • 打包时候显示引用的模块,--display-modules
  • 查看为什么要打包,--display-reasons
  • 打包字体有颜色,--colors

四、资源管理

  1. 处理css文件
  • 安装css-loader和style-loader
npm install css-loader style-loader --save-dev
  • 第一种方式:新建style.css,并在index.js引用,在文件中直接用
require('style-loader!css-loader!./style.css');
  • 第二种方式:打包时候用参数的方式。
    不在require中用style-loader!css-loader!的写法,则需要在打包的时候加上--module-bind参数,即: webpack src/hello.js dist/bundle.js --module-bind 'css=style-loader!css-loader'
require('./style.css');
  • 第三种方式:配置文件方式添加css-loader
const path=require('path');
module.exports={
    entry:'./src/index.js',
    output:{
        path:path.resolve(__dirname, './dist'),
        filename:'bundle.js'
    },
    module:{
        rules:[
            {
                test:/\.css$/,
                use:[
                    'style-loader',
                    'css-loader'
                ]
            }
        ]
    }
}

使用postcss-loader和 autoprefixer
安装

npm install postcss-loader autoprefixer --save-dev
const path=require('path');
module.exports={
    entry:'./src/index.js',
    output:{
        path:path.resolve(__dirname, './dist'),
        filename:'bundle.js'
    },
    module:{
        rules:[
            {
                test:/\.css$/,
                use:[
                    'style-loader',
                    'css-loader',
                    'postcss-loader'
                ]
            }
        ]
    },
    postcss:[
      require('autoprefixer')({
            broswers:['last 5 versions']
        })
    ],
}

使用less和sass

npm install less-loader --save-dev
const path=require('path');
module.exports={
    entry:'./src/index.js',
    output:{
        path:path.resolve(__dirname, './dist'),
        filename:'bundle.js'
    },
    module:{
        rules:[
            {
                test:/\.less$/,
                use:[
                    'style-loader',
                    'css-loader',
                    'less-loader'
                ]
            }
        ]
    }
}
  1. 处理images
npm install --save-dev file-loader
  • webpack.config.js中,module->rules添加节点
            {
                test:/\.(png|svg|jpg|gif)$/,
                use:[
                    'file-loader'
                ]
            }
  1. 处理fonts
            {
                test:/\.(woff|woff2|eot|ttf|otf)$/,
                use:[
                    'file-loader'
                ]
            }
  1. 处理数据
       {
         test: /\.(csv|tsv)$/,
         use: [
           'csv-loader'
         ]
       },
       {
         test: /\.xml$/,
         use: [
           'xml-loader'
         ]
       }
  1. 使用babel
    安装npm install babel-loader babel-core babel-preset-env
module: {
  rules: [
    {
      test: /\.js$/,
      exclude: /(node_modules|bower_components)/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env'],
          plugins: [require('@babel/plugin-transform-object-rest-spread')]
        }
      }
    }
  ]
}

四、管理输出(Output Management)

  1. 最新的目录结构
  webpack-demo
  |- package.json
  |- webpack.config.js
  |- /dist
  |- /src
    |- index.js
    |- print.js
  |- /node_modules
  • src目录下新建print.js
export default function printMe(){
    console.log('I get called from print.js!');
}
  • index.js修改
import printMe from './print'
function compoent(){
    var ele=document.createElement('div');
    
    var btn=document.createElement('button');
    ele.innerHTML="Hello Webpack!";
    btn.innerHTML='Click me and check the console!';
    btn.onclick=printMe;
    ele.appendChild(btn);
    return ele;
}
document.body.appendChild(compoent());
  • webpack.config.js
const path=require('path');
module.exports={
    entry:{
        app:'./src/index.js',
        print:'./src/print.js'
    },
    output:{
        path:path.resolve(__dirname, './dist'),
        filename:'[name].bundle.js'
    }

}
  • 运行npm run build,会生成app.bundle.js`print.bundle.js
  1. 使用HtmlWebpackPlugin
    可以使得html引用的文件自动跟随改变。
npm install --save-dev html-webpack-plugin
  • webpack.config.js
const path=require('path');
const HtmlWebPackPlugin=require('html-webpack-plugin');
module.exports={
    entry:{
        app:'./src/index.js',
        print:'./src/print.js'
    },
    plugins:[
        new HtmlWebPackPlugin({
            title:'Output Management'
        })
    ],
    output:{
        path:path.resolve(__dirname, './dist'),
        filename:'[name].bundle.js'
    }

}
  • 运行打包后,将会在dist文件夹下替换原来的index.html,并且title变为配置中的Output Management
  1. 根据html模板生成html
  • 在根目录新建index.html,用ejs的写法对title传参
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
</html>
  • webpack.config.js
    plugins:[
        new HtmlWebPackPlugin({
            template:'index.html',
            inject:'head',//默认body,false则不自动插入
            filename:'index-[hash].html',
            title:'webpack is good!'
            // minify:{压缩
            //     removeComments:true,删除注释
            //     collapseWhitespace:true  删除空格
            // }
        })
    ],
  • 运行打包后将生成index.html,自动插入两个ouput文件
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>webpack is good!</title>
  <script type="text/javascript" src="app.bundle.js"></script><script type="text/javascript" src="print.bundle.js"></script></head>
  <body>
</html>
  1. 清除/dist目录
  • 安装 clean-webpack-plugin
npm install clean-webpack-plugin --save-dev
  • webpack.config.js
const CleanWebpackPlugin = require('clean-webpack-plugin');

    plugins: [
+     new CleanWebpackPlugin(['dist']),
      new HtmlWebpackPlugin({
        title: 'Output Management'
      })
    ],

五、开发

  1. source-map定位出错的代码,webpack.config.js
    entry:{
        app:'./src/index.js',
        print:'./src/print.js'
    },
+   devtool:'inline-source-map',
  1. 观察模式
    1)可以用--watch参数,文件改动后将自动运行打包。

2) webpack-dev-server在文件改动后,实现浏览器自动刷新

npm install --save-dev webpack-dev-server

3) 修改webpack.config.js,告诉开发服务器在哪找文件

    devtool: 'inline-source-map',
+   devServer: {
+     contentBase: './dist'
+   },

4) 配置的意思是告诉webpack-dev-server,在 localhost:8080 下建立服务,将 dist 目录下的文件,作为可访问文件。在package.json添加start命令,如下:

    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
      "watch": "webpack --progress --watch",
     "start": "webpack-dev-server --open",
      "build": "webpack"
    },

5) 运行npm start即可开启服务。

  1. webpack-dev-middleware,是一个中间件容器(wrapper),它将通过 webpack 处理后的文件发布到一个服务器(server),配合express使用。

六、模块热更新

  1. 启用webpack内置的HMR,修改webpack.config.js
  const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');
  const CleanWebpackPlugin = require('clean-webpack-plugin');
+ const webpack = require('webpack');

  module.exports = {
    entry: {
-      app: './src/index.js',
-      print: './src/print.js'
+      app: './src/index.js'
    },
    devtool: 'inline-source-map',
    devServer: {
      contentBase: './dist',
+     hot: true
    },
    plugins: [
      new CleanWebpackPlugin(['dist']),
      new HtmlWebpackPlugin({
        title: 'Hot Module Replacement'
      }),
+     new webpack.NamedModulesPlugin(),
+     new webpack.HotModuleReplacementPlugin()
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };

七、Tree Shaking移除未引用的代码

针对使用 ES2015 模块语法(即 import 和 export)才能精简,运行后会压缩。

  • 安装
npm i --save-dev uglifyjs-webpack-plugin
  • 修改配置webpack.config.js
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
 },
 plugins: [
   new UglifyJSPlugin()
 ]
};

八、生产环境构建

目标是:更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。

  1. 配置
  • 生产环境与开发环境的配置分开,同时保留通用的配置。用 webpack-merge,安装:
npm install --save-dev webpack-merge
  • 删掉webpack.config.js,新建webpack.common.js
 const path = require('path');
 const CleanWebpackPlugin = require('clean-webpack-plugin');
 const HtmlWebpackPlugin = require('html-webpack-plugin');

 module.exports = {
   entry: {
     app: './src/index.js'
   },
   plugins: [
     new CleanWebpackPlugin(['dist']),
     new HtmlWebpackPlugin({
       title: 'Production'
     })
   ],
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist')
   }
 };
  • webpack.dev.js
 const merge = require('webpack-merge');
 const common = require('./webpack.common.js');

 module.exports = merge(common, {
   devtool: 'inline-source-map',
   devServer: {
     contentBase: './dist'
   }
 });
  • webpack.prod.js
 const merge = require('webpack-merge');
 const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
 const common = require('./webpack.common.js');

 module.exports = merge(common, {
   plugins: [
     new UglifyJSPlugin()
   ]
 });
  1. npm scripts
    修改package.json,npm run build指定为生产环境,npm start指定为生产环境
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --config webpack.prod.js",
    "watch": "webpack --watch",
    "start": "webpack-dev-server --open --config webpack.dev.js"
  },
  1. minification
  • 压缩代码方面,除了UglifyJSPlugin,还可以选择BabelMinifyWebpackPlugin,ClosureCompilerPlugin这两者。
  1. source map
    开发环境中可以用inline-source-map,但生产环境要这样用
  • webpack.prod.js
  const merge = require('webpack-merge');
  const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
  const common = require('./webpack.common.js');

  module.exports = merge(common, {
+   devtool: 'source-map',
    plugins: [
-     new UglifyJSPlugin()
+     new UglifyJSPlugin({
+       sourceMap: true
+     })
    ]
  })
  1. 指定环境
    通过指定process.env.NODE_ENV === 'production'来指定生产环境,在有些时候可以区分dev和prod
  • webpack.prod.js
+ const webpack = require('webpack');
  const merge = require('webpack-merge');
  const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
  const common = require('./webpack.common.js');

  module.exports = merge(common, {
    devtool: 'cheap-module-source-map',
    plugins: [
      new UglifyJSPlugin({
        sourceMap: true
      }),
+     new webpack.DefinePlugin({
+       'process.env': {
+         'NODE_ENV': JSON.stringify('production')
+       }
+     })
    ]
  })

在开发时可以这样用来调试,src/index.js

  import { cube } from './math.js';
+
+ if (process.env.NODE_ENV !== 'production') {
+   console.log('Looks like we are in development mode!');
+ }

  function component() {
    var element = document.createElement('pre');

    element.innerHTML = [
      'Hello webpack!',
      '5 cubed is equal to ' + cube(5)
    ].join('\n\n');

    return element;
  }

  document.body.appendChild(component());
  1. cli替代选项
    在命令中添加参数也可以起到配置的作用

1) 参数--optimize-minimize表示引用UglifyJSPlugin插件来打包。
2) --define process.env.NODE_ENV="'production'"起到上面DefinePlugin的作用。
3) webpack -p 将同时使用上面两项。

九、代码分离

  1. 入口起点
  2. 防止重复
    使用 CommonsChunkPlugin分离公共的模块,生成common.bundle.js
  • webpack.config.js
  const path = require('path');
+ const webpack = require('webpack');
  const HTMLWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
    entry: {
      index: './src/index.js',
      another: './src/another-module.js'
    },
    plugins: [
      new HTMLWebpackPlugin({
        title: 'Code Splitting'
-     })
+     }),
+     new webpack.optimize.CommonsChunkPlugin({
+       name: 'common' // 指定公共 bundle 的名称。
+     })
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };

其他代码分离的插件:

  • ExtractTextPlugin: 用于将 CSS 从主应用程序中分离。
  • bundle-loader: 用于分离代码和延迟加载生成的 bundle。
  • promise-loader: 类似于 bundle-loader ,但是使用的是 promises。
  • CommonsChunkPlugin 插件还可以通过使用显式的 vendor chunks 功能,从应用程序代码中分离 vendor 模块。
  1. 动态导入
  2. bundle分析
  3. 下一步

十、懒加载

  1. 示例
  2. 框架

十一、缓存

  1. 输出文件的文件名
  • chunkhash表示在文件内容不变的情况下,hash不变。
  • hash表示每次打包都会生成一个不同的文件名。
    output: {
-     filename: 'bundle.js',
+     filename: '[name].[chunkhash].js',
      path: path.resolve(__dirname, 'dist')
    }
  1. 提取模板
  • CommonsChunkPlugin 可以用于将模块分离到单独的文件中,将 webpack 的样板(boilerplate)和 manifest 提取出来.webpack.config.js
  const path = require('path');
+ const webpack = require('webpack');
  const CleanWebpackPlugin = require('clean-webpack-plugin');
  const HtmlWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
    entry: './src/index.js',
    plugins: [
      new CleanWebpackPlugin(['dist']),
      new HtmlWebpackPlugin({
        title: 'Caching'
-     })
+     }),
+     new webpack.optimize.CommonsChunkPlugin({
+       name: 'runtime'
+     })
    ],
    output: {
      filename: '[name].[chunkhash].js',
      path: path.resolve(__dirname, 'dist')
    }
  };

打包时将生成一个runtime.bundle.js。

  • 将第三方库(library)(例如 lodash 或 react)提取到单独的 vendor chunk 文件中,是比较推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。因此通过实现以上步骤,利用客户端的长效缓存机制,可以通过命中缓存来消除请求,并减少向服务器获取资源,同时还能保证客户端代码和服务器端代码版本一致。这可以通过使用新的 entry(入口) 起点,以及再额外配置一个 CommonsChunkPlugin 实例的组合方式来实现:
  var path = require('path');
  const webpack = require('webpack');
  const CleanWebpackPlugin = require('clean-webpack-plugin');
  const HtmlWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
-   entry: './src/index.js',
+   entry: {
+     main: './src/index.js',
+     vendor: [
+       'lodash'
+     ]
+   },
    plugins: [
      new CleanWebpackPlugin(['dist']),
      new HtmlWebpackPlugin({
        title: 'Caching'
      }),
+     new webpack.optimize.CommonsChunkPlugin({
+       name: 'vendor'
+     }),
      new webpack.optimize.CommonsChunkPlugin({
        name: 'runtime'
      })
    ],
    output: {
      filename: '[name].[chunkhash].js',
      path: path.resolve(__dirname, 'dist')
    }
  };

将生成一个vendor bundle。

注意,引入顺序在这里很重要。CommonsChunkPlugin 的 'vendor' 实例,必须在 'runtime' 实例之前引入。

  1. 模块标识符
  2. bundle 会随着自身的新增内容的修改,而发生变化。

vendor bundle 会随着自身的 module.id 的修改,而发生变化。
runtime bundle 会因为当前包含一个新模块的引用,而发生变化。
第一个和最后一个都是符合预期的行为 -- 而 vendor 的 hash 发生变化是我们要修复的。幸运的是,可以使用两个插件来解决这个问题。第一个插件是 NamedModulesPlugin,将使用模块的路径,而不是数字标识符。虽然此插件有助于在开发过程中输出结果的可读性,然而执行时间会长一些。第二个选择是使用 HashedModuleIdsPlugin,推荐用于生产环境构建:

十二、创建Library

  const path = require('path');
  const webpack = require('webpack');
  const CleanWebpackPlugin = require('clean-webpack-plugin');
  const HtmlWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
    entry: {
      main: './src/index.js',
      vendor: [
        'lodash'
      ]
    },
    plugins: [
      new CleanWebpackPlugin(['dist']),
      new HtmlWebpackPlugin({
        title: 'Caching'
      }),
+     new webpack.HashedModuleIdsPlugin(),
      new webpack.optimize.CommonsChunkPlugin({
        name: 'vendor'
      }),
      new webpack.optimize.CommonsChunkPlugin({
        name: 'runtime'
      })
    ],
    output: {
      filename: '[name].[chunkhash].js',
      path: path.resolve(__dirname, 'dist')
    }
  };

现在,不管再添加任何新的本地依赖,对于每次构建,vendor hash 都应该保持一致:

十三、创建Library

webpack可以创建一个library,详情看文档。

十四、Shimming

十五、TypeScript

十六、迁移到新版本

十七、使用环境变量

十八、构建性能

  1. 保持版本最新
  2. Loaders,使用 include 字段仅将 loader 模块应用在实际需要用其转换的位置中:
{
  test: /\.js$/,
  include: path.resolve(__dirname, "src"),
  loader: "babel-loader"
}
  1. 解析
  • 尽量减少 resolve.modules, resolve.extensions, resolve.mainFiles, resolve.descriptionFiles 中类目的数量,因为他们会增加文件系统调用的次数。
  • 如果你不使用 symlinks ,可以设置 resolve.symlinks: false (例如 npm link 或者 yarn link).
  • 如果你使用自定义解析 plugins ,并且没有指定 context 信息,可以设置 resolve.cacheWithContext: false 。
  1. Dlls
    使用 DllPlugin 将更改不频繁的代码进行单独编译。这将改善引用程序的编译速度,即使它增加了构建过程的复杂性。
  2. Smaller = Faster
    减少编译的整体大小,以提高构建性能。尽量保持 chunks 小巧。
  • 使用 更少/更小 的库。
  • 在多页面应用程序中使用 CommonsChunksPlugin。
  • 在多页面应用程序中以 async 模式使用 CommonsChunksPlugin 。
  • 移除不使用的代码。
  • 只编译你当前正在开发部分的代码。
  1. Worker Pool
    thread-loader 可以将非常消耗资源的 loaders 转存到 worker pool 中。
  2. 持久化缓存
    使用 cache-loader 启用持久化缓存。使用 package.json 中的 "postinstall" 清除缓存目录。
  3. 自定义 plugins/loaders
  4. Development环境推荐
    9.1 增量编译

使用 webpack 的监听模式。不要使用其他工具来监听你的文件和调用 webpack 。在监听模式下构建会记录时间戳并将信息传递给编译让缓存失效。

在某些设置中,监听会回退到轮询模式。有许多监听文件会导致 CPU 大量负载。在这些情况下,你可以使用 watchOptions.poll 来增加轮询的间隔。
9.2 在内存中编译
以下几个实用工具通过在内存中进行代码的编译和资源的提供,但并不写入磁盘来提高性能:
webpack-dev-server
webpack-hot-middleware
webpack-dev-middleware
9.3 Devtool
在大多数情况下,cheap-module-eval-source-map 是最好的选择。
9.4 避免在生产环境下才会用到的工具
某些实用工具, plugins 和 loaders 都只能在构建生产环境时才有用。例如,在开发时使用 UglifyJsPlugin 来压缩和修改代码是没有意义的。以下这些工具在开发中通常被排除在外:

UglifyJsPlugin
ExtractTextPlugin
[hash]/[chunkhash]
AggressiveSplittingPlugin
AggressiveMergingPlugin
ModuleConcatenationPlugin
9.5 最小化入口 chunk

  1. Production推荐的配置
    10.1 多个编译时

parallel-webpack: 它允许编译工作在 worker 池中进行。
cache-loader: 缓存可以在多个编译时之间共享。
10.2 少用source maps

十九、开发Vagrant

二十、管理依赖

二十一、publicPath

二十二、集成

「一键投喂 软糖/蛋糕/布丁/牛奶/冰阔乐!」

fengxianqi

(๑>ڡ<)☆谢谢老板~

使用微信扫描二维码完成支付

版权声明:本文为作者原创,如需转载须联系作者本人同意,未经作者本人同意不得擅自转载。
添加新评论
暂无评论