web 前端未分类

webpack

2018-08-13  本文已影响329人  谷子多

模块打包器:开发一个项目,业务逻辑会很多,开发会按照功能逻辑拆分成一个个的模块,这样开发的时候更加有条理,维护起来也会更加方便。

但这样就会涉及到一个问题,模块之间会有复杂的依赖关系,在处理这些模块依赖的时候,后端的开发有着得天独厚的条件,模块化是天生支持的。

模块打包器会先分析项目依赖,然后按照复杂的规则把它们打包在一起,当然这些规则是隐藏起来的,不需要知道是怎么打包的,只需要知道这个打包器专门会把你的模块依赖的代码都打包到一起,输出一个新文件,你会得到新的js文件。

它不仅能帮你打包js文件,还有其他资源文件,都会视为模块,都会打包。

但如果只是打包,那就太小瞧webpack了,它有着很强大的生态,有各种loader,来帮你处理文件的内容,比如编译语法,处理路径,还有插件辅助你开发和项目构建,从而加快你的开发效率,如果你在开发一个大型单页应用,它的代码分割功能对页面的性能是意义非凡的,从项目的起始到项目上线,它参与了整个的项目周期。

更新部分:
NamedModulesPlugin =》 optimization.namedModules;CommonsChunkPlugin =》 optimization.splitChunks, optimization.runtimeChunk

名词:


核心概念:

module.exports = {
    entry : 'index.js'
    ====
    entry:['index.js','vendors.js'] //创建多个入口
    ====
    // 推荐,扩展性好,直接新增即可
    entry : {
        index : 'index.js',// index对应的是index.js
        vendor : 'verdor.js'
    }
}
module.exports = {
    entry : {
        index : 'index.js',// index对应的是index.js
        vendor : 'verdor.js'
    }
    output : {
        filename : 'index.min.js'
        ==
        //多个:自定义规则
        // [name]对应入口文件,hash是打包过程后的版本号
        filename:'[name].min.[hash:5].js'
        
    }
}

主要负责:

属性:

const config = {
  output: {
    filename: 'my-first-webpack.bundle.js'
  },
  module: {
    rules: [
//以txt结尾的文件,用raw-loade转换
      { test: /\.txt$/, use: 'raw-loader' }
    ]
  }
};
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]

webpack使用

打包

一、打包JS

命令

webpack entry<entry> output
===
webpack --config wenpack.conf.js

示例1:es6模块化打包

//app.js,入口文件
import sum from './sum'
console.log('sum(23,24) = ', sum(23,24))

终端输入命令:

webpack app.js bundle.js

这时候就打包出了一个bundle.js,在index中引入这个js文件即可。


屏幕快照 2018-08-09 下午4.44.19.png

示例2:common.js打包

//minus.js
module.exports = function(a,b){
    return a - b
}
//app.js
let minus = require('./minus')
console.log('minus(24,17) = ', minus(24,17))

执行命令:webpack app.js bundle.js,可以看到成功的被打包了。


屏幕快照 2018-08-09 下午4.57.26.png

示例2:AMD打包

//muti.js
define(function(require, factory) {
    'use strict';
    return function(a,b){
        return a*b
    }
});
//app.js
require(['muti'],function(muti){
    console.log('muti(2,3)=',muti(2,3))
})

打包成功!!!这时候发现打包出了两个文件:0.bundle.js和bundle.js

//webpack.config.js
module.exports = {
    entry : {
        app : './app.js'
    },
    output:{
        filename : '[name].[hash:5]'
    }
}

这时候打包了两个文件:


屏幕快照 2018-08-09 下午6.21.45.png

二、编译 ES6

babel

npm install babel-loader@8.0.0-beta.0 @babel/core

新建index.html,webpack.config.js

    module:{
        rules : [
            {
                test:/\.js$/,
                use:'babel-loader',
                // 排除在规则之外的不编译
                exclude:'/node_module/'
            }
        ]

    }

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

为babel指定presets编译es5,es5....

rules : [
            {
                test:/\.js$/,
                use:{
                    loader:'babel-loader',
                    options:{
                        // 给loader指定presets
                        presets:['@babel/preset-env',{
                            targets:{
                                browsers:['>1%','last 2 versions']
                            }
                        }]
                    }                    
                },
                exclude:'/node_module/'
            }

打包,看结果:


屏幕快照 2018-08-10 上午12.43.43.png

可以看出,es6语法被转成了es5。

babel的两个插件: babel plyfill , babel runtime transform

babel-preset-es2015 是一个babel的插件,用于将部分ES6 语法转换为ES5 语法。但是babel-preset并不会转换promise、generator等函数,我们还要引入babel-polify库。
项目中现在一般直接使用babel-preset-env,她整合了babel-preset-es2015, babel-preset-es2016, and babel-preset-es2017,而且可以配置需要支持的浏览器/运行环境,仅转化需要支持的语法,使文件更轻量

插件作用:解决实现低版本函数和方法不支持。
- Generator
- set
- Map
- Arry.from
- Array.protptype.includes

2、使用

{
    "presets" : [
        ["@babel/preset-env",{
            "targets":{
                "browsers":["last 2 versions"]
            }
        }]
    ],
    "plugins": ["@babel/transform-runtime"]
}

三、编译 Typescript:js

Typescript:js的超集,来自微软,需要使用对应的loader。

{
    "compilerOptions": {
        "module": "commonjs",
        // 指定编译后的文件的运行环境
        "target":"es5",
        "allowJs": true
    },
    // 路径
    "include": [
        "./src/*"
    ],
    // 指定不编译的部分
    "exclude": [
        "./node_modules"
    ]
}
//webpack.config.js
module.exports = {
    entry : {
        'app' : './src/app.ts'
    },
    output : {
        filename:'[name].bundel.js'
    },
    module : {
        rules : [
            {
                test : /\.tsx?$/,
                user : {
                    loader : 'ts-loader'
                }
            }
        ]
    }
}
// app.ts
const NUM = 45

interface Cat {
    name : String,
    gender : String
}

function touchCat (cat:Cat){
    console.log('miao~',cat.name)
}

touchCat({
    name : 'tom',
    gender : 'male'
})

打包公共代码 : SplitChunksPlugin

现在都是模块化开发,这样就会有模块互相依赖的情况,公共模块就是公共代码。
SplitChunksPlugin的登场就是为了抹平之前CommonsChunkPlugin的痛的,它能够抽出懒加载模块之间的公共模块,并且不会抽到父级,而是会与首次用到的懒加载模块并行加载,这样我们就可以放心的使用懒加载模块了.
SplitChunksPlugin的好,好在解决了入口文件过大的问题还能有效自动化的解决懒加载模块之间的代码重复问题
目的

!!!!CommonsChunkPlugin已被弃用
        // 代码合并
        new webpack.optimize.CommonsChunkPlugin({
            // 生成的文件
            name : 'common',
            minChunks : 2
        })
!!!现在用SplitChunksPlugin
        new webpack.optimize.SplitChunksPlugin({
            name : 'common',
            minChunks: 2,
        })
!!!或者写成以下(和plugins同级)
optimization: { 
        splitChunks: { 
            cacheGroups: { 
                commons: {
                     name: "common", 
                     chunks: "initial", 
                     minChunks: 2 
                } 
            } 
        } 
    }

这时候打包发现并没有把公共部分提取出来。
附详细配置

new webpack.optimize.SplitChunksPlugin({
                    chunks: "initial", // 必须三选一: "initial" | "all"(默认就是all) | "async"
                    minSize: 0, // 最小尺寸,默认0
                    minChunks: 1, // 最小 chunk ,默认1
                    maxAsyncRequests: 1, // 最大异步请求数, 默认1
                    maxInitialRequests: 1, // 最大初始化请求书,默认1
                    name: function () {
                    }, // 名称,此选项可接收 function
                    cacheGroups: { // 这里开始设置缓存的 chunks
                        priority: 0, // 缓存组优先级
                        vendor: { // key 为entry中定义的 入口名称
                            chunks: "initial", // 必须三选一: "initial" | "all" | "async"(默认就是异步)
                            name: "vendor", // 要缓存的 分隔出来的 chunk 名称
                            minSize: 0,
                            minChunks: 1,
                            enforce: true,
                            maxAsyncRequests: 1, // 最大异步请求数, 默认1
                            maxInitialRequests: 1, // 最大初始化请求书,默认1
                            reuseExistingChunk: true // 可设置是否重用该chunk(查看源码没有发现默认值)
                        }
                    }
                })

代码分割和懒加载

在前端的优化过程中,一个很常见的手段就是对代码切分,让用户在浏览的时候加载更少的代码,通过代码分割和懒加载,可以节省加载时间,如果用户只浏览一个页面的时候却下载了所有的代码,那么就会对用户的带宽造成浪费,影响浏览时间。
代码分割和懒加载是一回事,wenpack会自动把代码分割之后再把需要加载的代码加载过来。
这两个虽然是webpack的功能,但是并不在webpack的配置中。
实现方式

require.ensure(['lodash'],function () {
    // 此处require是异步,不是commonjs
    var _ = require('lodash')
},'vendor') 

当两个模块都依赖了第三方模块的时候,
可以提前把第三方模块放到父模块里,这样动态加载两个模块的时候,由于父模块已经有了这个第三方模块,所以不会重复加载。比如a,b依赖c,将c在引用ab的父级模块中用此方法先引入,就可以把c模块提取在这个父级模块中。

import('./subPageA').then(function(){
})

业务场景

一、代码分割
- 分离业务代码和第三方依赖
- 分离业务代码业务公共代码和第三方依赖
- 分离首次加载和访问后加载的代码(提高首屏加载速度)

代码演示:

import './subpageA'
import './subpageB'
// vendor指定chunk名称
// 外层ensure只是将lodash加载到了页面中,并不执行
// 在回调中require才是真正的执行
// ensure中可以省略不写参数ensure(['']
// 打包结果 : lodash被提取到了vendor,实现了第三方依赖了业务代码的分离
require.ensure(['lodash'],function () {
    // 此处require是异步,不是commonjs
    var _ = require('lodash')
    _.join(['1','2'],'3')
},'vendor') 
export default 'pageA'
//import './subpageA'
//import './subpageB'
require.ensure(['./subpageA'],function (params) {
    let subpageA  = require('./subpageA')
},'subpageA')

require.ensure(['./subpageB'],function (params) {
    let subpageA  = require('./subpageB')
},'subpageB')


require.ensure(['lodash'],function () {
    // 此处require是异步,不是commonjs
    var _ = require('lodash')
    _.join(['1','2'],'3')
},'vendor') 
export default 'pageA'
屏幕快照 2018-08-12 上午3.39.24.png
这时候发现,c没有被单独打包出来.
把c模块先引进,这样就把c模块提取到了引用ab模块的父级模块上。
概括一下 : a,b依赖c,将c在引用ab的父级模块中用此方法先引入,就可以把c模块提取在这个父级模块中。
require.include('./moduleA')
.......(省略代码和上面的代码一致)

前端面试之webpack面试常见问题

概念问题一:什么是webpack和grunt和gulp有什么不同

Webpack是一个模块打包器,他可以递归的打包项目中的所有模块,最终生成几个打包后的文件。他和其他的工具最大的不同在于他支持code-splitting、模块化(AMD,ESM,CommonJs)、全局分析。

问题二:什么是bundle,什么是chunk,什么是module?

答案:bundle是由webpack打包出来的文件,chunk是指webpack在进行模块的依赖分析的时候,代码分割出来的代码块。module是开发中的单个模块。

问题三:什么是Loader?什么是Plugin?

答案:
1)Loaders是用来告诉webpack如何转化处理某一类型的文件,并且引入到打包出的文件中
2)Plugin是用来自定义webpack打包过程的方式,一个插件是含有apply方法的一个对象,通过这个方法可以参与到整个webpack打包的各个流程(生命周期)。

问题:如何可以自动生成webpack配置?

答案: webpack-cli /vue-cli /etc ...脚手架工具

问题一:webpack-dev-server和http服务器如nginx有什么区别?

答案:webpack-dev-server使用内存来存储webpack开发环境下的打包文件,并且可以使用模块热更新,他比传统的http服务对开发更加简单高效。

问题二:什么 是模块热更新?

答案:模块热更新是webpack的一个功能,他可以使得代码修改过后不用刷新浏览器就可以更新,是高级版的自动刷新浏览器。

化问题一:什么是长缓存?在webpack中如何做到长缓存优化?

答案:浏览器在用户访问页面的时候,为了加快加载速度,会对用户访问的静态资源进行存储,但是每一次代码升级或是更新,都需要浏览器去下载新的代码,最方便和简单的更新方式就是引入新的文件名称。在webpack中可以在output纵输出的文件指定chunkhash,并且分离经常更新的代码和框架代码。通过NameModulesPlugin或是HashedModuleIdsPlugin使再次打包文件名不变。

化问题二:什么是Tree-shaking?CSS可以Tree-shaking吗

答案:Tree-shaking是指在打包中去除那些引入了,但是在代码中没有被用到的那些死代码。在webpack中Tree-shaking是通过uglifySPlugin来Tree-shaking
JS。Css需要使用Purify-CSS。

上一篇下一篇

猜你喜欢

热点阅读