webpack学习笔记(二)

1 支持es6语法 

(注意直接写map等es6语法,由于现代浏览器是支持的,所以可以显示出来,可以执行 npm run build 命令,在生成的文件中,查看剪头函数,promise,async/awiat等es6语法是没有被转成es5的)

【注意 map方法是es5中的方法】

1.1 首先安装 babel-loader和babel的核心库: npm install -D bable-loader @babel/core   bable-loader 是连接 webpack 和 babel 的桥梁

module.exports = {
    module: {
        rules: [{ 
            test: /.js$/, 
            exclude: /node_modules/,  //排除这个文件夹下的内容 
            loader: 'babel-loader',
        }]
    }
}

1.2 安装    npm install @babel/preset-env -D     preset-env 包含了es6转换成es5的规则

module.exports = {
    module: {
        rules: [{ 
            test: /.js$/, 
            exclude: /node_modules/, 
            loader: 'babel-loader',
            options: {
                presets: ['@babel/preset-env']  //在这里配置使用loader的配置项
            }
        }]
    }
}

1.3 此时诸如 let等已经转成H5 了,还有一些es6语法不能转过去,比如箭头函数,使用polyfill

 npm install --save @babel/polyfill

1.4 在业务代码中引入: 

import "@babel/polyfill"

由于会把所有的转换方法打包,所以会导致js文件变的非常大,而我们只用了几个es6的语法

module.exports = {
    module: {
        rules: [{ 
            test: /.js$/, 
            exclude: /node_modules/, 
            loader: 'babel-loader',
            options: {
                presets: [['@babel/preset-env',{ //这里要给preset-env设置参数 所以要放在数组中
                    useBuiltIns:'usage' //根据业务代码使用的es6语法 来添加对应的转换方法,且使用改方法后 就是按需引用 polyfill 不用在文件中 再次引用 polyfill
                }]]  
            }
        }]
    }
}

还可以配置转换的环境,进一步优化缩小打包代码:

//针对各个浏览器的最新的两个版本,以及safari的版本7及以上,针对以上两个条件的兼容情况进行代码转译
{
  "presets": [
    ["@babel/preset-env", 
    {
"targets": { "browsers": ["last 2 versions", "safari >= 7"] } }]
]
}

 1.5 由于babel的可配置项特别多,所以可以将其提取出来放在 .babelrc 文件中,然后把options的对象整个放在这个文件中

注意1:

根据babel的网址: https://www.babeljs.cn/repl  可以在线调试转成es5的情况

左侧是es6的代码,选择转成es2015,转的范围是 chrome 67, 由于chrome支持es6语法,所以不用转译;

上图选择的是 ie9 的环境,可见左侧的es6语法已经转译成右侧的es5语法了;

new Promise 没有改变;map语法是es5的不用转变;

---------------------------------------

注意2:使用了  "useBuiltIns":"usage" 之后,自动按需引入 pollyfill,所以文件中不用再引用 polyfill,

首先在文件中使用es6语法:

let listData = [1,2,3,4]
listData.includes(4);

示例1:

{
  "presets":[
    ["@babel/preset-env",{
    "useBuiltIns":"false",
    "targets":{
        "browsers":[">1%","last 2 version","not ie <= 8"]
      }
    }]
  ]
}

写成false之后,不在引入 polyfill, 则编译后的结果:

可以看到 includes 没有被转译

示例2:

{
  "presets":[
    ["@babel/preset-env",{
    "useBuiltIns":"usage",
    "targets":{
        "browsers":[">1%","last 2 version","not ie <= 8"]
      }
    }]
  ]
}

即按需引入 polyfill ,在看转译的文件,全局搜索 includes:

全局有48个,includes,说明前面已经做过转译了!!!

【注意:如果进行过代码分割,也就是生成了新的js文件来存放来自于 module_node 的代码,比如 vue babel等,需要在生成的vendor文件中查找。比如 async include 等es6 特性的方法,在业务代码中使用与否,在vendor文件中,查找该单词的数量确实不一样】

-----------------------------------------

注意3:

1:

Babel 7 的相关依赖包需要加上 @babel scope。一个主要变化是 presets 设置由原来的 env 换成了 @babel/preset-env, 可以配置 targetsuseBuiltIns 等选项用于编译出兼容目标环境的代码。其中useBuiltIns如果设为 "usage",Babel 会根据实际代码中使用的ES6/ES7代码,以及与你指定的targets,按需引入对应的 polyfill,而无需在代码中直接引入 import '@babel/polyfill'避免输出的包过大,同时又可以放心使用各种新语法特性。

{
  "presets": [
    ["@babel/preset-env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      },
      "useBuiltIns": "usage"
    }]
  ]
}

配置文件中:

{
      test:/.js$/,
      exclude: __dirname + 'node_modules',
      use: 'babel-loader'  //注意这里要用的是 use 不要直接使用 loadder 否则转化es5 不生效!!!!!!!
}

2:    webpack4.x下babel的安装、配置及使用

3: 如果打包时有下面的警告:

 npm install --save core-js@2

需要配置corejs

{
  "presets":[
    ["@babel/preset-env",{
    "useBuiltIns":"usage",
    "corejs":2,
    "targets":{
        "browsers":[">1%","last 2 version","not ie <= 8"]
      }
    }]
  ],
  "plugins": ["@babel/plugin-syntax-dynamic-import"]
}
开源库zloirock/core-js
提供了es5、es6的polyfills,包括promises、symbolscollections、iterators、typed arraysECMAScript 7+ proposalssetImmediate 等等。
如果使用了 babel-runtime、babel-plugin-transform-runtime 或者 babel-polyfill,你就可以间接的引入了 core-js 标准库

3: 配置打包时报Cannot read property 'bindings' of null 或 Cannot find module '@babel/core'问题

解决:模块中对js的处理配置如下图可解决

    

或者使用:

{
            test:/.js$/,
            exclude: /('node_modules')/,
            use: 'babel-loader'
  }

2 使用webpack 搭建react的运行环境

2.1  首先安装react  npm install react react-dom --save 

npm install --save-dev @babel/preset-react

然后在 .babelrc

{
    presets:[
        [
            "@babel/preset-env",{
                targets:{
                    chrome:"67",
                },
                useBuiltIns: 'usage'
            }
        ],
        "@babel/preset-react"  //执行顺序是 从下往上 从右往左
    ]
}

注意的是:

如果在babel的配置中写了    useBuiltIns: 'usage'  (只使用用到了的方法) 则会自动引入 import  "@babel/polyfill"  所以不需要在业务代码中再次引入。

3 tree shaking 只打包引用的文件代码,例如定义 build.js:

export const add = (a,b)=>{
    console.log(a+b)
}

export const minus = (a,b)=>{
    console.log(a-b)
}

在其他文件中引入:import { add } from './build.js' 

则在生成的文件中,你会发现即使没有调用 minus方法,也会把整个的build.js 进行打包编译。为了只对使用过的方法进行打包,进行如下操作:

首先明确的是  tree shaking 只对  import { add } from './build.js'  起作用,而对  const add = require ('./build.js') 不起作用

注意的是:export和export default的区别:

export default 只能导出一个对象,该对象可以包含多个变量,但是不能包含多个函数,例如不能向下面这样:

const myadd = (a,b)=>{
  console.log('myadd');
  return a+b;
}
const myminus = (a,b)=>{
  console.log('myminus');
  return a-b;
}

export default{
  myadd,
  myminus
}

export 可以导出多个变量和函数,对应的按需引入import { add } from './build.js' 

module.exports = {
    mode: 'development',
    module: {
    },
    plugins: [
    ],
    optimization: {
        usedExports: true //在这里进行配置
    },
    output: {
    }
}

但是这样的话对于 没有 引入具体 函数的:  import '@babel/polly-fill'  会不进行打包

所以要在package.json配置文件中 添加: "sideEffects":["@babel/polly-fill"] ,即对不需要使用 tree-shaking 进行操作的文件,放在这里,

但是一般我们不需要在文件中 引入这些的话 ,需要tree-shaking 对所有import的文件进行 按需处理 ,所以这里写成false : "sideEffects":false 

但是对于没有具体引出的 css: import './index.css' ,json配置文件中需要重新定义: "sideEffects":["*.css"] 

最后 对于开发模式,虽然能够识别,但是便于开发调试,没有删掉未引用的代码,在上线的模式 中:

mode: 'development', //开发环境

mode: 'production' // 上线环境
// tree-shaking 已经把 optimization 写好了 
// 所以删除
// optimization: {
//     usedExports: true //在这里进行配置
// },

然后搜索打包后的js文件,发现没有引入的方法不再有了。

树摇 只能处理 export = {} 然后 import {*} from '/';的代码;比如 import {join} from 'lodash';
则无法处理,因为其内部不是用标准的 export 导出的
 
import {join} from 'lodash';
const myadd = (a,b)=>{
  console.log('myadd');
  return a+b;
}
const myminus = (a,b)=>{
  console.log('myminus');
  return a-b;
}

const myjoin = (a,b)=>{
  console.log('myjoin');
  return join([a,b],'@@')
}
export{
  myadd,
  myminus,
  myjoin
}

例如上面,引入的 lodash库,并不能随着树摇而去掉。

 
 
 

4 对webpack的配置文件进行模式区分:新建两个配置文件,其中 webpack.dev.js 的 mode是 mode: 'development' 

{
    "scripts":{
        "dev":"webpack-dev-server --config webpack.dev.js",
        "build":"webpack --config webpack.prod.js"
    }
}

4.1 由于两个文件重复性很大,所以把共有的代码放在新建的文件中: webpack.common.js;

这样就会有三个配置文件 分别是 共性的代码  webpack.common.js;开发的配置文件 webpack.dev.js;上线的文件 webpack.prod.js。

然后使用webpack-merge进行文件的合并: npm install webpack-merge -D 

const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');

const devConfig = {
    mode:'',
    devtool:'',
    devServer:{

    }
}
module.exports = merge(commonConfig,devConfig); 
//这样使用 merge 合并后导出 commonConfig 和 devConfig

 如果把这三个配置文件放在build文件夹下,则config.json中相应的修改:

 "dev":"webpack-dev-server --config ./build/webpack.dev.js" 

   同样配置文件中也要修改:

{
    //CleanWebpackPlugin插件会默认当前文件为根路径,
    //所以需要用root参数,配置当前的根路径
    plugins:[
        new CleanWebpackPlugin(['dist'],{  //dist路径是在root相对下的路径
            root:path.resolve(__dirname,'../') 
        })
    ],
    output:{
        filename:'[name].js',
        path:path.resolve(__dirname,'../dist') //这做修改里也要
    }
}

5. code Splitting 即代码拆分  比如要提取出公共的 lodash代码

5.1 首先安装  npm install lodash --save  该库可以用来操作一些字符串,

比如:

import _ from 'lodash';
var element = document.createElement('div');
element.innerHTML = _.join(['Dell', 'Lee'], '-');
document.body.appendChild(element);

然后在配置文件中,在和entry,module等并列的位置:

optimization:{
  splitChunks:{
      chunks:'all'
  }
}

就可以自动打包出vendor.js;

5.2 异步引入的js库

function getComponent() {
    return import('lodash').then(({ default: _ }) => {
        var element = document.createElement('div');
        element.innerHTML = _.join(['Dell', 'Lee'], '-');
        return element;
    })
}

getComponent().then(element => {
    document.body.appendChild(element);
});

但是打包不支持,所以安装: npm install babel-plugin-dynamic-import-webpack //动态引入import 

因为引入了新的babel,所以要在babelrc文件中修改:

{
    presets: [
        [
            "@babel/preset-env", {
                targets: {
                    chrome: "67",
                },
                useBuiltIns: 'usage'
            }
        ],
        "@babel/preset-react"
    ],
   plugins: ["dynamic-import-webpack"] //修改了这里
}

代码分割和webpack无关,

webpack 中实现代码分割,两种方式:

1. 同步代码: 只需要在webpack.common.js 中做 optimization 的配置

2. 异步代码: 无需做任何配置需要安装上述插件;

以上promise代码还可以简化成async模式:

async function getComponent() {
  const { default:_ } = await import( /* webpackChunkName:"lodash" */ 'lodash' );
  var element = document.createElement('div');
  element.innerHTML = _.join(['Dell', 'Lee'], '-');
  return element;
}

getComponent().then(element => {
 document.body.appendChild(element);
});

5.3 以上不是官方插件,所以不要看上面的了,看下面的官方插件(在babel的官网上):

首先安装: npm install --save-dev @babel/plugin-syntax-dynamic-import    然后在.babelrc文件中增加

plugins: ["@babel/plugin-syntax-dynamic-import"]

然后使用方式为:

function getComponent() {
    return import(/* webpackChunkName:"lodash" */ 'lodash').then(({ default: _ }) => {
        var element = document.createElement('div');
        element.innerHTML = _.join(['Dell', 'Lee'], '-');
        return element;
    })
}

getComponent().then(element => {
    document.body.appendChild(element);
});

config文件中:

optimization: {
        splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendors:false ,
        default:false
      }
    }
}

更多配置:

optimization: {
  splitChunks: {
    chunks: 'all',//同步异步全都打包
    minSize: 30000,//打包的库或者文件必须大于这个字节才会进行拆分
    minChunks: 1,//规定当模块在生成的js文件(trunk)中被调用过多少次的时候再进行拆分
    maxAsyncRequests: 5,
    maxInitialRequests: 3,
    automaticNameDelimiter: '~',//如果不写filename 默认名字 组名~[name]
    name: true,
    cacheGroups: {//缓存组,因为需要打包完成之后,在把所有要拆分的代码合并拆分,所以先要缓存
      vendors: {
        test: /[\/]node_modules[\/]/, //如果上面chunks定为all,就是找到所有的import文件,看他是不是调用于 node_modules 文件夹 是的话就拆分
        priority: -10,//优先级 比如同时符合vender 和 default 这个优先级高 所以存在这里
        filename: 'vendors.js', //拆分后打包的文件名字
      },
      default: {//像文件中 import进来的文件 如果不在 node_modules文件夹中 则走默认组,打包出的文件名字是 common.js
        priority: -20,
        reuseExistingChunk: true,//比如a.js 引用了 b.js;如果b.js在之前已经被拆分过,则这里不再对其进行拆分
        filename: 'common.js'
      }
    }
  }
}
原文地址:https://www.cnblogs.com/xiaozhumaopao/p/10658920.html