webpack入门之二--资源输入和输出

126 阅读7分钟

1.资源处理过程

在一切流程的最开始,我们需要指定一个或多个入口(entry),也就是告诉Webpack具体从源码的哪个文件开始打包。这些存在依赖关系的模块会被打包时封装为一个chunk。由这个chunk得到的打包产物我们一般称之为bundle。chunk name是在入口配置,bundle name是在配置

image.png

定义多个入口时,一般情况下会打包生成多个bundle,也就是说,entry和bundle存在着一一对应的关系

image.png

2.配置资源入口

1.context

context可以理解为资源入口的路径前缀,在配置时要求必须使用绝对路径的形式

//入口的位置是:工程根目录/src/index.js
module.exports = {
    context:path.join(__dirname,'./src'),
    entry:'./index,js'
}

配置context的主要目的是让entry编写地更加简洁,尤其是在多入口的情况下。context可以省略,默认值为当前工程的根目录

2.entry

与context只能为字符串不同,entry的配置可以有多种形式:字符串、数组、对象、函数

1.字符串类型入口
module.exports = {
    entry:'./src/index.js',
    output:{filename:bundle.js},
    mode:'development'
}
2.数组类型入口

传入一个数组的作用是将多个资源预先合并,在打包时Webpack会将数组中的最后一个元素作为实际的入口路径

module.exports = {
    entry:['babel','./src/index.js']
}

//相当于以下代码

//webpack.config.js
module.exports = {
    entry:'./src/index.js'
}
//index.js
import 'babel'
3.对象类型入口

如果要定义成多入口,则必须使用对象形式。对象的属性名是chunkname,对象的属性值是入口路径

module.exports = {
    entry:{
        index:'./src/index.js',
        lib: './src/lib.js'
    }
}

对象的属性值也可以是字符串或数组。在使用字符串或数组定义单入口时,并没有办法改变chunkname,只能为默认的'main'。在使用对象来定义多入口时,则必须为每一个入口定义chunk name

4.函数类型入口

用函数定义入口时,只要返回值是上面介绍的任何配置形式即可。函数的优点是可以在函数体里添加一些动态逻辑来获取工程的入口,并且也支持返回一个Promse对象来进行异步操作

module.exports = {
    entry:() => './src/index.js'
}
5.实例
5.1单页应用

对于单页应用来说,一般是定义单一入口即可

module.exports = {
    entry:'./src/app.js'
}

无论是框架、库、还是各个页面的模块,都由app.js单一入口进行引用。这样做的好处是只产生一个JS文件,依赖关系清晰。而这种做法也有弊端,即所有模块都打包到一起,当应用规模上升到一定程度之后会导致产生的资源过大,降低用户的页面渲染速度。

Webpack默认配置中,当一个bundle大于250kb(未压缩时)时会认为这个bundle已经过大了,在打包时会发生警告

image.png

试想一下,假如工程只产生一个JS文件并且体积很大,一旦代码发生更新,即便只有一点点改动,用户都要重新下载整个资源文件,这对于页面的性能是很不友好的。我们可以提取vendor的方法。vendor的意思是‘供应商’,在Webpack中vendor一般指的是工程所用的库、框架等第三方模块集中打包而产生的bundle

module.export = {
    entry:{
        app:'./src/app.js',
        vendor:['react','react-dom']
    }
}

上面的配置中,app.js和最开始一样。但是我们增加了一个新的chunkname为vendor入口,并通过数组将工程所依赖的第三方模块放进去。但是我们没有为vendor设置入口路径,Webpack要如何打包,这会在之后介绍到的组件中使用

5.2多页应用

对于多页应用来说,为了尽可能减小资源的体积,我们希望每个页面都只加载各自必要的逻辑,而不是将所有页面打包到同一个bundle中,因此每个页面都需要有一个独立的bundle,这种情形可以用多入口来实现。

同样,也可以通过提取vendor的方法,将各页面之间的公共模块进行打包

module.exports = {
    entry:{
        pageA:'./src/pageA.js',
        pageB:'./src/pageB.js',
        vendor:['react','react-dom']
    }
}

在上面的配置中,入口与页面是一一对应的关系,这样每个HTML只要引入各自的JS就可以加载所需要的模块了

3.配置资源出口

所有与出口相关的配置都在output对象中,我们只介绍几个常用的

3.1 filename

顾名思义,filename的作用是控制输出资源的文件名,其形式为字符串。

module.exports = {
    entry:'./src/app.js',
    output:{
        filename:'bundle.js'
    }
}

filename不仅仅是bundle的名字,还可以是一个相对路径,即便路径中的目录不存在也没有关系,Webpack会在输出资源时创建该目录。

在多入口场景中,我们需要为对应产生的每个bundle制定不同的名字,webpack支持使用一种类似模板语言动态地生成文件名。

module.exports = {
    entry:{
        app:'./src/app.js',
        vendor:'./src/vendor.js'
    },
    output:{
        filename:'[name].js'
    }
}

在资源输出时,上面配置的name就会被替换成chunk name,实际打包出来的资源就是vendor.js和app.js。还有集中模板变量可以用于filename

image.png

上述变量一般有如下两个作用:

  • 当有多个chunk存在时对不同的chunk进行区分。如name,chunkhash,id这些对每个chunk来说都是不同的
  • 控制客户端缓存。hash和chunkhash都与chunk内容直接相关,当chunk内容改变时,可以同时引起文件名的更改,从而使用户在下一次请求资源时立刻下载新版本。query也可以起到类似的效果,但是它与chunk内容无关,要由开发者手动指定

3.2 path

path可以指定资源输出位置,要求值必须为绝对路径。它会将生成的文件放入我们配置的路径中

module.exports = {
    entry:{
        app:'./src/app.js'
    },
    output:{
        filename:'[name].js',
        path:path.join(__dirname,'dist')
    }
}

3.3 publicPath

publicPath是一个非常重要的配置项,并且容易和path相混淆。从功能上来说,path用来指定资源的输出位置,而publicPath则用来指定资源的请求位置。让我们详细解释这两个定义:

  • 输出位置:打包完成后资源产生的目录,一般将其指定为工程中的dist目录
  • 请求位置:由JS或者CSS所请求的间接资源路径。页面中的资源分为两类,一种是HTML页面直接请求的,比如通过script标签加载的JS;另一种是由JS或CSS请求的,如异步加载的JS、从CSS请求的图片字体等。publicPath的作用就是指定这一部分间接资源的请求位置。
1.HTML相关

与HTML相关,我们可以将pubulicPath指定为HTML的相对路径,在请求这些资源时会以当前页面HTML所在路径加上相对路径,构成实际请求的URL

image.png

2.Host相关

若publicPath的值以‘/’开始,则代表此时publicPath是以当前页面的host name为基础路径的。

image.png

3.CDN相关

上面两种方式都是以相对路径配置publicPath,也可以用绝对路径配置。这种情况一般发生于静态资源放在CDN上,由于CDN的域名和当前域名不一致,需要以绝对路径的形式进行指定。当publicPath以协议头或相对协议的形式开始时,代表当前路径是CDN相关

image.png

webpack-dev-server的配置中也有一个publicPath,这个publicPath和webpack的配置完全不同,它的作用是指定webpa-dev-server的静态资源服务路径。

module.exports = {
    entry:{
        app:'./src/app.js'
    },
    output:{
        filename:'bundle.js',
        path:path.join(__dirname,'dist')
    },
    derServer:{
        publicPath:'/assets/',
        port:3000
    }
}

上面的代码可以看到,webpack配置中output.path为dist目录,因此bundle.js应该生成在dist目录。但是我们启动webpack-dev-server的服务后,访问loccalhost:3000/dist/bundle.js时却得到404。这是因为我们配置了webpack-dev-server并指定了publicPath,因此我们的打包的资源(bundle.js)相当于被放到了loccalhost:3000/assets下面(实际上只是内存,并没有生成bundle.js),所以只有访问loccalhost:3000/assets/bundle.js

为了避免开发环境和生产环境不一致而造成开发者疑惑,webpack-dev-server的publicPath应该和webpack中的output.path保持一致

2.实例

对于单入口文件来说,我们不用设置动态的output.filename,直接指定输出文件名为bundle.js即可

对于多入口来说,必然用到模板变量来配置filename