webpack 基础入门

1,180 阅读10分钟

我相信,有不少的朋友对webpack都有或多或少的了解。网上也有了各种各样的文章,文章内作者也写出了不少自己对于webpack这个工具的理解。在我刚刚接触webpack的时候,老实说,网上大部分的文章我是看不懂的。。webpack里面有很多名词,是没有接触和理解过模块化的同学都难以理解的。我感觉,学习任何一项新技术,要弄清楚为什么使用它,它是什么,它有什么用等概念,弄清楚这些概念之后,我相信,在日后的webpack学习中会达到事半功倍的效果。这篇文章,我会以最简单的方式,阐述什么是webpack。当然,这是我个人对webpack的一些理解,也是在学习中总结。

另外,最好的学习webpack的资源是webpack的官网。传送门:webpack

当然,如果你早已是webpack的实践者,对webpack认识足够深入,这篇文章不太适合您阅读。如果你是小白,那就可以开启webpack的探索之路了。

webpack是目前流行的一款模块化打包工具。

webpack定义:一款前端资源模块化和打包工具。

webpack作用:

1. 将许多松散的模块按照依赖关系和规则打包成符合生产环境环境部署的前端资源。
2. 将按需加载的模块进行代码分割,等到实际需要的时候再异步加载。
3. 通过加载器loader的转换,所有的前端资源都可以看作是模块。比如说CommonJS模块,AMD模块,ES6模块,css,sass,json,图片等。

短短的几句话,就有太多让人难以明白的地方。

1. 什么是前端资源?

2. 什么是模块?

3. 什么是模块化?

4. 什么叫按需加载?

5. 如何实现按需加载?

6. 什么是plugins?

7. 什么是loader?

什么是前端资源

所谓前端资源,就是我们在创建html时,引入的script,link,img,json等文件。webpack足够的优秀,只需要在html文件中引入一个js文件,在定义一个入口文件js,用于存放依赖的模块,就可以将其他前端资源按照依赖关系和规则打包。

以前我们需要这样来引入文件。

<link rel="stylesheet" href="style/stylesheets/screen.css"  media='screen'/ >
<script src='script/jquery-2.2.1.min.js'></script>
<script src='script/bootstrap.js'></script>
<script src="script/index.js" ></script>

index.js依赖bootstrap.js,而bootstrap又依赖于jquery。我们必须按照DOM顺序来写每一个js文件。

现在只需要在html中引入一个主文件index.js,其他依赖的前端资源都写在另外一个入口文件js中,这个入口文件不用写在html中,然后配置好config,在cmd中输入webpack执行编译,所有前端资源都被引入了。并且webpack会帮我们入口文件entry.js的每个模块的类型和依赖关系,等到需要的时候再按需加载。

//html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script src='js/main.js'></script>
<!-- 引入引入主模块 main.js -->
</body>
</html>

//entry.js
import $ from 'jquery';
import other...

什么是模块

在webpack中,所有的前端资源都是模块,可以通过加载器loader进行转换。在javascript方面,有几大模块系统,CommonJS模块,AMD模块,CMD模块,ES6模块。谈谈我对这几大模块的理解。

CommonJS

在CommonJS中,有一个全局方法require(),用于加载依赖模块。

//main.js
var jquery = require('jquery');
var bootstrap = require('bootstrap');

主文件main.js模块依赖于这两个模块,CommonJS缺点就是同步加载。也就是说,会先加载jquery模块,等到jquery加载完了再去加载bootstrap模块。引用阮一峰老师的话

同步加载意味着阻塞加载,当依赖的模块太大时,浏览器会处于假死的状态。

假死意味着浏览器任然处在加载中,仍然还是空白页面。这种假死的状态带来的后果就是用户的离开。

AMD模块

AMD也叫异步模块定义,英文Asynchronous Module Definition。AMD是requirejs对模块定义时的产出。它采用异步方式加载模块,每个独立模块的加载不影响回调函数中定义的模块的运行。在回调函数中定义一个模块,只有当依赖的模块加载完成之后,该模块才会编译执行。AMD也采用require()语句来加载一个模块,但是不同于CommonJS,它有两个参数。第一个参数是数组,需要传入依赖模块;第二个参数是回调函数,回调函数中也接受参数,而参数是形式参数,来自于每一个依赖模块。举个例子。

//main.js
require(['jquery','bootstrap'],function($, boot){
    //写入的模块
})

主模块main.js依赖于jquery,Bootstrap模块,main只有在这两个模块加载完成之后才会编译执行main定义的模块,属于同步加载。而在两个依赖模块中,属于异步加载。也就是说bootstrap不用等到jquery加载完成之后再加载,被依赖的模块是哪一个模块小,就先加载哪一个,这就避免了CommonJS模块中同步加载依赖模块而出现浏览器假死的状态。稍微总结一下,
主模块需要等到依赖模块加载完成之后才编译执行,属于同步加载;而被依赖模块之间属于异步加载,哪一个模块小,就先加载哪一个。

AMD模块的一大不足就是所有依赖的模块都需要提前加载,依赖前置。

CMD模块

CMD模块跟AMD很相似,这里引用玉伯老师的话看看CMD和AMD的区别。

对于依赖的模块,AMD是提前执行,CMD是延迟执行。

CMD推从依赖就近,AMD推从依赖前置

依赖就近的意思就是当我需要某个模块的时候再去异步加载。也就是按需要加载前端资源,懒加载。
可以用八个字来总结CMD。依赖就近,延迟执行。

ES6模块

ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。ES6中与CommonJS,AMD模块一个区别在于ES6是通过import关键字来输入某个模块提供的功能,export或者export default规定该模块的对外接口,通俗易懂一些,也就是暴露该模块定义的一些属性和方法,供其他模块调用。

ES6和CommonJS,AMD模块最大的区别在于,ES6模块可以实现编译时加载,简单的说就是按需加载。而后两种方法只能是运行时加载。上一段代码。

ES6:
import {get, post, ajax} from 'jQuery';

CommonJS:
var 

什么是模块化

webpack是支持以上的模块系统的。在头脑中要形成某种技术的知识框架才能学好该技术,所以花费了一些时间做了些介绍。模块化就是webpack使用某种方法将每一个松散的模块按照依赖关系编译的过程。webpack需要一个入口js文件,主模块js文件,config文件就可以实现前端资源模块化。看个简单的例子。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script src='js/main.js'></script>
1. <!-- 引入主模块 main.js -->
</body>
</html>

2. //入口文件 entry.js 
document.write("<h1>I'm Uncke Keith!</h1>");

3. //webpack.config.js
module.exports = {
    entry :'./app/js/entry.js',
    output : {
        path: './dist/js'
        filename: 'main.js'
    }
};

4. //打开cmd,在文件目录下,输入webpack执行编译就可以看到document.write()的结果了。

什么叫按需加载?

webpack的其中一个作用就是可以将按需加载的模块进行代码分割,根据实际需要进行异步加载。按需加载,顾名思义就是按照用户需要某个功能的时候在加载相应的模块。举个例子,当我们在浏览一些图片网站的时候,如果图片在你打开该网站的时候就全部一起加载好,那造成的后果就是页面会保持一段时间的空白状态,直到全部图片加载完成之后才会显示。如果是按需要加载,就可以在用户刚进入页面的时候加载可视区域窗口的图片,当用户拖动滚动条下拉的时候再去加载图片,这样不仅减少HTTP请求,同时提高了页面加载速度。在举一个例子。在单页应用中,为了减少http请求次数,会把所有js文件合并为一个文件,这样请求数量减少了,可是请求的文件体积却变大了。按需加载就可以解决这个问题。

如何实现按需加载?

ES6的一大涉及思想就是想让前端资源静态化,在编译的过程中,而非运行过程中就确定模块的依赖关系。webpack在编译的过程中,就会对整个代码进行静态分析,分析出各个模块的类型和它们依赖关系,然后将不同类型的模块提交给适配的加载器来处理。比如一个用 Sass 写的样式模块,可以先用 Sass-Loader加载器将它转成一个CSS 模块,在通过 CSS 模块把他插入到页面的 style 标签中执行。并且在你编译的时候就确定了依赖关系。

什么是plugins?

webpack跟我们提供了很多内置的插件,可以实现loader做不到的事情。在这里介绍几个常用的插件。

1. extract-text-webpack-plugin

我们在入口文件中import(或者require)进一些css文件时,webpack会帮我们把css样式与其他前端资源打包到output的filename文件中,然后在head标签中会自动加载一个style标签。但是,我们可能会需要独立出一个css文件,这时候就需要使用extract-text-webpack-plugin插件了。具体用法如下:

var ExtractTextPlugin = require("extract-text-webpack-plugin");
//这里需要使用CommonJS语法来引入一个依赖。

module.exports = {
    module: {
        loaders: [
            { test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader") }
            //loader的执行顺序是 先执行css-loader给css文件加上地址,然后执行style-loader在头部加上style标签。
        ]
    },
    plugins: [
        new ExtractTextPlugin("styles.css")
        //使用plugins选项对象,使用new调用ExtractTextPlugin构造函数,传入的参数是需要单独生成的css文件,路径与output中的path路径相同。详细的参数可以到extract-text-webpack-plugin官网查看。
    ]
}

当我们成功生成了单独的css文件之后,就可以通过link标签引入了。

什么是loader?

Webpack 本身只能处理原生的 JavaScript 模块,但是 loader 转换器可以将各种类型的资源转换成 JavaScript 模块。这样,任何资源都可以成为 Webpack 可以处理的模块。每一个loader都会针对入口文件entry.js的依赖模块引入的前端资源进行转换。站在更高的角度上看问题,我们在webpack.config.js里面的所有配置都是针对入口文件的,始终记住这点很重要。因为这就是我们配置webpack.config.js的目的。在这里介绍一些常用loader。

1. postcss-loader

我是一名sass实践者,并且用着一个封装了很多mixins的compass库,这个库的最大好处就是可以直接include一些compass封装好的的css3的mixin时,编译之后会帮我们加上css前缀。如果想在.vue中也实现这种自动前缀的功能,可以使用webpack给我们提供的postcss-loader。(可以去官网查看相关介绍)。postcss?这又是什么鬼,其实postcss只是一个平台,我们需要用的是基于postcss平台上的一些常用的插件。

如果想使用这个插件,需要下载一些依赖。

cnpm install autoprefixer --save-dev
//autoprefixer用于添加css3前缀

具体的webpack.config.js配置如下

//注意先引入依赖
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var autoprefixer = require('autoprefixer'); 

module.exports = {
    entry: __dirname + '/app/entry.js',
    output: {
        path: __dirname + '/dist',
        filename: 'bundle.js'
    },
    module: {
        config...
    },
    postcss:[
        autoprefixer()
    ]
};

如果你的入口文件entry.js是这样的。

...
import './assets/main.scss';

那么当然你在main.scss使用scss语法写一些css3属性时,在编译之后就可以看到css3前缀了。但是如果你的样式是写在*.vue组件里面的

比如说,入口文件entry.js是这样的。

...
import app from './app.vue';

topBar.vue

<template>
    config...
</template>

<script>
    config...
</script>
<style lang='sass' scoped>
    .main{
        font-weight: bold;
        p {
            border-radius: 10px;
            box-shadow: 3px 3px 3px #ccc;
            transition: all 0.3s;   此处测试css3的属性前缀
        }
    }
</style>

在浏览器中查看效果,你会发现,.vue组件中定义的style样式并没有加上属性前缀。

.main p[data-v-a64cfc10] {
    background-color: red;
    border-radius: 10px;
    box-shadow: 3px 3px 3px #ccc;
    color: red;
    display: inline-block;
    font-weight: bold;
    height: 100px;
    transition: all 0.3s ease 0s;
}

难道是postcss-loader失效了?其实并不是。出现这种情况的原因主要还是对vue-loader不熟悉导致的。因为你是把样式写在了单个*.vue组件中,所以这里会涉及另外一个lodaer,也就是Vue官方提供的vue-loader。在vue-loader中如果也想让样式拥有前缀,在webpack.config.js要进行如下配置。

module.exports = {
    entry: ... ,
    output: { ... },
    module: { ... },
    这里配置vue指的是vue-loader,我们需要在vue-loader中再一次配置postcss。
    vue: {
        postcss: [require('autoprefixer')()],
        autoprefixer: true
    },
    postcss:[
        autoprefixer()
    ]
    //也就是说,postcss需要配置两处。一是解析entry.js中引入的css模块;一处是解析
    单个*.vue组件的<style>标签中的样式。
}

执行编译,你会发现正常显示了。

持续更新中..