转:webpack令人困惑的地方和CDN

463 阅读9分钟

webpack的核心原理

  1. 一切皆模块 正如js文件可以是一个‘模块’一样,其他的(如CSS,image,html)文件也可以视为模块。因此,你可以require('myJSfile.js')也可以require('.css')。这意味着我们可以将事物(业务)分割成更小的易于管理的片段,从而达到重复利用的目的。

  2. 按需加载

传统的模块打包工具(module bundlers)最终将所有的模块比编译成一个庞大的bundle.js文件。但是在真实的app里边,‘bundle.js’文件可能有10M-15M之大,而且异步加载部分代码以实现按需加载。

开发模式和生产模式

首先需要知道webapck有许多特性,一些是‘开发模式’下才有的,一些是‘生产模式’下才有的,还有一些是两种模式下都有的。

为了生成bundles文件,你可能在package.json文件加入如下的script项

'script':{
   "build":"webpack --config webapck.config.prod.js"
   "dev":"webapck-dev-server"
}

webpack CLI和webapck-dev-server

值得注意的是,webpack作为模块打包工具,提供两种用户交互接口

  1. webapck cli tool --- 这是默认的接口(随着webpack的而存在于.bin目录中)

  2. webapck-dev-server tool --- 一个node.js server(你需要单独安装它)

webapck CLI(非常适合做production build)

这个命令行工具通过cli接收一些配置option,或者通过config file来接受这些option(默认为webapck.config.js配置文件),这些配置选项将用于webpack的打包过程。

虽然你可能是通过cli来开始学习webapck的,但是实际上命令行工具更多用于创建生产环境下使用的bundle

用法如下:

OPTION 1:
//全局安装
npm install webpack --g
//在命令行使用时,使用webpack生成包
webpack

OPTION2:
//局部安装
npm install webpack --save
//将其添加至package.json的script下
“scripts”: {
 “build”: “webpack --config webpack.config.prod.js -p”,
 ...
 }
//使用方法
"npm run build"
webapck-dev-server(非常适合创建开发的build,并serve静态的assets)

webpack-dev-server是一个运行于8080的端口的express nodejs server,这个server会自己调用webpack本身实现构建,并且server构建出来的bundle,这个server的好处是他可以提供比如“Live Reloading”或者“Hot Module Replacement(HMR)”的实用功能

npm install webpack-dev-server --save
OPTION1:
//通过命令行使用
webpack-dev-server --inline --hot

OPTION2:
//添加至package.js的script
“scripts”: {
 “start”: “webpack-dev-server --inline --hot”,
 ...
 }
 //使用
 npm run start
 open browser at:
 http://localhost:8080
webpack vs webpack-dev-server options

注意像inline和hot这些选项时webapck-dev-server特有的,而另外的如hind-modules则是CLI模块特有的选项

wepack-dev-server CLI选项和配置项 另外需要注意的是你可以通过下面两种方式向webpack-dev-server传入参数:

  1. 通过webpack.config.js文件的‘devserver’对象
  2. 通过CLI选项
// 通过CLI传参
webpack-dev-server --hot --inline
// 通过webpack.config.js传参
devServer: {
inline: true,
hot:true
}

有时候devServer配置项(hot: true 和inline: true)不生效,所以,我更偏向于使用如下的方式向cli传递参数

// package.json
{
    "scripts": "webpack-dev-server --hot --inline"
}

注意:确定没有同时传入hot: true 和-hot

webpack-dev-server的“hot” 和 “inline”选项

'inline'选项会为入口页面添加‘热加载’功能,‘hot’则开启‘热替换’,即尝试重新加载组件改变的部分(而不是重新加载整个页面)。如果两个参数都传入,当资源改变时,erbpack-dev-server将会先尝试热替换,如果失败则重新加载整个入口页面。

//当资源发生改变,以下三种情况会生成新的bundle,但是又有区别
// 不会刷新浏览器
webpack-dev-server
//刷新浏览器
webpack-dev-server --inline
//重新加载改变的部分,HRM失败则刷新页面

“entry”:值分别是字符串,数组和对象的情况

entry配置项告诉webpack应用的根模块或起始点在哪里,而且不同的类型有不同的目的。

像绝大多数app一样,倘若你的应用只有一个单一的入口,enter项的值你可以使用任意类型,最终输出的结果都是一样的

entry-array

但是,如果你想追加多个互不依赖的文件,那么可以使用array格式

比如:你可能需要‘googleAnalytics.js’到你的html中,那么你可以告诉webpack追加改analytics.js到bundle.js的后面

entry-object

现在,假设你的应用是多页面的(multi-page application)而不是SPA,有多个html文件(index.html和profile.html)。然后你通过一个对象告诉webpack为每一个html生成一个bundle文件

以下的配置将会生成两个js bundle文件,indexEntry.js和profileEntry.js分别应用于index.html和profile.html中

使用方法为:

//profile.html
<script src=”dist/profileEntry.js”></script>
//index.html
<script src=”dist/indexEntry.js”></script>
entry-combination(混合类型)

你也可以在object entry中使用array entries。例如,下面的config将产生3个文件vendor.js和index.js, profile.js:

output:‘path’项和‘publicPath’项

output项告诉webpack怎样存储输出结果以及存储到哪里。output的两个配置项‘path’和‘publicPath’可能会造成困扰。

‘path’仅仅是告诉webapck结果存储在哪里,然而‘publicPath’项则被许多webpack的插件用在生产模式下更新内嵌到css、html文件里的url值 例如:在你的css中,你可能又一个url从你的localhost来load'./test.png'。但是在生产环境下,test.png文件可能存在于CDN中,这意味着你可能需要手工的更新这些url以便在生产环境中可以之下那个正确的url地址

为了解决这个手工修改url的问题,你可以使用webpack重的publicPath参数,而这个参数对几乎所有的plugin都是能够理解并使用这个publicPath参数的,并且自动在创建production build是更新这些url

// Development: Both Server and the image are on localhost
.image { 
  background-image: url(‘./test.png’);
 }
// Production: Server is on Heroku but the image is on a CDN
.image { 
  background-image: url(‘https://someCDN/test.png’);
 }
loaders and chaning loaders模块加载和链式模块加载

loaders是一些额外的node modules专门用于帮助‘load’或者‘import’各种类型的文件到浏览器可以认识的js,css文件格式。更进一步,loader也允许通过require或者import来引入这些文件到js中

例如L你可以使用bable-loader来转换es6写的js代码到浏览器可以认识的es5代码

module: {
 loaders: [{
  test: /\.js$/, ←Test for ".js" file, if it passes, use the loader
  exclude: /node_modules/, ←Exclude node_modules folder
  loader: ‘babel’ ←use babel (short for ‘babel-loader’)
 }]

chaining loaders(从右往左):链式(管道)的加载器 多个loader可以用在同一个文件上并且被链式调用。链式调用时从右到左执行且loader之间用“!”来分割

例如,我们如果有一个“macss.css”文件,我们需要将它的内容dump到html的css content中,我们可以通过一下两个loaders来实现这个功能:css-loader和style-loader

module: {
 loaders: [{
  test: /\.css$/,
  loader: ‘style!css’ <--(short for style-loader!css-loader)
 }]

下面这张图可以解释这个过程是如何工作的

  1. 首先webpack在module中检索相关依赖,webpack看到mycssfile.css通过require来做import,因此mycssfile.css将作为dependency来处理,webpack首先将该css文件给到'css-loader'来做处理

  2. css-loader加载所有的css内容以及该css内容中自己的depency(比如@import othercss),并保存为json. webpack然后将这个结果传给style-loader继续处理。

  3. style-loader则接收这个json,并且增加css contents并将这个字符串插入到index.html文件中

loaders也可以进行配置

loaders可以通过传入不同的patameters以不同的方式来工作

在下面的栗子中,我们配置url-loader当image小于1024字节的话,直接使用DataURLs。我们可以传入limit参数来指定这个大小

.babelrc文件

babel-loader使用“presets”配置选项使se6转为es5的过程,一级如何解析react的jsx到js文件,我们可以通过query参数传入配置

module: {
  loaders: [
    {
      test: /\.jsx?$/,
      exclude: /(node_modules|bower_components)/,
      loader: 'babel',
      query: {
        presets: ['react', 'es2015']
      }
    }
  ]
}

然而,很多项目中babel的配置项目可能很多,这种情况下,你就可以把balble的这些配置项目放到.babelrc文件中去。babel-loader将自动加载这个.babelrc文件如果它存在的话。

所以在很多例子中,你可能会看到:

//webpack.config.js 
module: {
  loaders: [
    {
      test: /\.jsx?$/,
      exclude: /(node_modules|bower_components)/,
      loader: 'babel'
    }
  ]
}

//.bablerc
{
 “presets”: [“react”, “es2015”]
}

插件

插件一般都是用来输出bundle的node模块

例如,uglifyJSPlugin获取bundle.js然后压缩和混淆内容以减小文件体积。

类似的extract-text-webpack-plugin内部使用css-loader和style-loader来收集所有的css到一个地方最终将结果提取结果到一个独立的”styles.css“文件,并且在html里边引用style.css文件。

//webpack.config.js
// 获取所有的.css文件,合并它们的内容然后提取css内容到一个独立的”styles.css“里
var ETP = require("extract-text-webpack-plugin");

module: {
 loaders: [
  {test: /\.css$/, loader:ETP.extract("style-loader","css-loader") }
  ]
},
plugins: [
    new ExtractTextPlugin("styles.css") //Extract to styles.css file
  ]
}

注意:如果你只是想把css使用style标签内联到html里,你不必使用extract-text-webpack-plugin,仅仅使用css loader和style loader即可:

module: {
 loaders: [{
  test: /\.css$/,
  loader: 'style!css' // (short for style-loader!css-loader)
 }]

加载器和插件

你可能已经意识到了,Loader处理单独的文件级别并且通常作用于包生成之前或生成的过程中。

而插件则是处理包(bundle)或者chunk级别,且通常是bundle生成的最后阶段。一些插件如commonschunkplugin甚至更直接修改bundle的生成方式。

处理文件的扩展名

很多Webpack的配置文件都有一个resolve属性,然后就像下面代码所示有一个空字符串的值。空字符串在此是为了resolve一些在import文件时不带文件扩展名的表达式,如require('./myJSFile')或者import myJSFile from './myJSFile'(译者注:实际就是自动添加后缀,默认是当成js文件来查找路径)

{
 resolve: {
   extensions: ['', '.js', '.jsx']
 }
}

CDN相关内容

内容分发网络(Content Delivery Network,简称CDN)

是建立并覆盖在承载网之上,由分布在不同区域的边缘节点服务器群组成的分布式网络

假设通过CDN加速的域名为www.a.com, 接入CDN网络,开始使用加速服务后,当终端用户(北京)发起HTTP请求时,处理流程如下:

  1. 当终端用户(北京)向www.a.com 下的指定资源发起请求时,首先向LDNS(本地DNS)发起域名解析请求。
  2. LDNS检查缓存中是否有www.a.com 的IP地址记录。如果有,则直接返回给终端用户;如果没有,则向授权DNS查询。
  3. 当授权DNS解析www.a.com 时,返回域名CNAME www.a.tbcdn.com 对应IP地址。域名解析请求发送至阿里云DNS调度系统,并为请求分配最佳节点IP地址。 4.LDNS获取DNS返回的解析IP地址。用户获取解析IP地址。用户向获取的IP地址发起对该资源的访问请求。 工作原理:
  4. CDN的加速资源是跟域名绑定的
  5. 通过域名访问资源,首先是通过DNS分查找离用户最近的CDN节点(边缘服务器)的IP
  6. 通过IP访问实际资源是,如果CDN上并没有缓存资源,则会到源站请求资源,并缓存到CDN节点上,这样,用户下一次访问的时候,该CDN节点那就会有对应资源的缓存了。