webpack学习笔记之优化缓存,以及js与css的hash指纹解耦方案

By admin in 4858美高梅 on 2019年3月29日

文件的hash指纹常常作为前端静态资源完毕增量更新的方案之一,Webpack是当下最流行的开源编写翻译工具之一,其强硬的功力也拉动诸多坑(当然,超过57%麻烦其实都足以在官方文书档案中找到答案)。

文件的hash指纹平日作为前端静态能源完结增量更新的方案之一,Webpack是近年来最盛行的开源编写翻译工具之一,其百战百胜的作用也推动众多坑(当然,大多数麻烦其实都能够在合法文书档案中找到答案)。

4858美高梅 1

除开的webpack基本配备,还足以更进一步助长配置,优化合并文件,加速编写翻译速度。上面是生产条件布署文件webpack.production.js,与wenbpack.config.js比较其不须求有个别dev-tools,dev-server和jshint校验等,将与付出有关的事物删掉。上面包车型地铁介绍均以此代码配置作参考。

譬如说,在Webpack编写翻译输出文件的布置进程中,假设供给为文件加入hash指纹,Webpack提供了五个布局项可供使用:hashchunkhash。那么双方有啥差异吧?其分别独立的利用场景又是哪些?本文结合作者工作中相遇的标题,不难记录一下上述难题的缓解方案。

例如,在Webpack编写翻译输出文件的布局进程中,假若急需为文件加入hash指纹,Webpack提供了七个布局项可供使用:hashchunkhash。那么双方有什么差异吗?其个别独立的选择场景又是怎么着?本文结合作者工作中遇见的难点,简单记录一下以上难题的化解方案。

logo

/*生成环境配置文件:不需要一些dev-tools,dev-server和jshint校验等。和开发有关的东西删掉*/
var webpack = require('webpack');
var path = require('path');
var node_modules = path.resolve(__dirname, 'node_modules');
var pathToReact = path.resolve(node_modules, 'react/dist/react.min.js');
var pathToReactDOM = path.resolve(node_modules, 'react-dom/dist/react-dom.min.js');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
//具体作用及缺点见plugins中的描述
//var WebpackMd5Hash = require('webpack-md5-hash');
// 该插件是对“webpack-md5-hash”的改进:在主文件中获取到各异步模块的hash值,然后将这些hash值与主文件的代码内容一同作为计算hash的参数,这样就能保证主文件的hash值会跟随异步模块的修改而修改。
var WebpackSplitHash = require('webpack-split-hash');

var config = {
  entry:{
    app:path.resolve(__dirname, 'src/js/entry.js'),
    mobile: path.resolve(__dirname, 'src/js/mobile.js'),
    //添加要打包在vendors.js里面的库
    vendors:['react','react-dom']
  },
  resolve:{
    alias: {
      'react.js': pathToReact, //alias:别名,
      'react-dom.js': pathToReactDOM
    },
    fallback: path.join(__dirname, "node_modules")
  },
  resolveLoader: { 
    fallback: path.join(__dirname, "node_modules") 
  },
  output:{
    path:path.resolve(__dirname, 'dist'),
    publicPath:'../',//生成的html里的引用路径用 publicPath
    //以文件内容的MD5生成Hash名称的script来防止缓存
    filename: 'js/[name].[chunkhash:8].js',
    //异步加载的模块是要以文件形式加载,生成的文件名是以chunkFilename配置的
    chunkFilename: 'js/[name].[chunkhash:8].js'
  },
  module:{
    loaders:[{
      test: /\.jsx?$/,
      //这里(node_modules文件夹)再也不需通过任何第三方来加载
      exclude:path.resolve(__dirname, 'node_modules'),
      loader: 'babel',
      query:{
        presets:['es2015', 'react']
      }
    },
    {
      test:/\.css$/,
      //loader:'style!css'
      loader: ExtractTextPlugin.extract("style", "css")
    },
    {
      test:/\.scss$/,
      loader:'style!css!sass'
    },
    //url-loader:图片、字体图标加载器,是对file-loader的上层封装,支持base64编码。传入的size(也有的写limit) 参数是告诉它图片如果不大于 25KB 的话要自动在它从属的 css 文件中转成 BASE64 字符串。
    {
      test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/,
      loader: 'url?limit=25000&name=[name].[ext]'
    }]
  },
  plugins:[
    //提取公共代码的插件,用于提取多个入口文件的公共脚本部分,然后生成一个vendors.js。注意HTML代码中要加载这个公共文件
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendors',
      filename: 'js/vendors.js'
    }),
    //在文件开头插入banner
    new webpack.BannerPlugin("The file is creted by yangmin.--"+ new Date()),
    //压缩js文件
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    }),
    /*插件:单独使用style标签加载css文件.contenthash代表的是文本文件内容的hash值,也就是只有style文件的hash值*/
    new ExtractTextPlugin("css/[name].[contenthash:8].css"),//设置其路径(路径相对于path)
    /*插件:动态生成html,在webpack完成前端资源打包以后,自动将打包后的资源路径和版本号写入HTML中,达到自动化的效果。*/
    new HtmlWebpackPlugin({
      filename:'view/index.html',  //生成的html存放路径,相对于 path
      template:'src/view/index.html',  //html模板路径
      inject:true,  //允许插件修改哪些内容,true/'head'/'body'/false,
      chunks:['vendors','app'],//加载指定模块中的文件,否则页面会加载所有文件
      hash:false,  //为静态资源生成hash值
      minify:{  //压缩HTML文件
        removeComments:false,  //移除HTML中的注释
        collapseWhitespace:false  //删除空白符与换行符
      }    
    }),
    new HtmlWebpackPlugin({
      filename:'view/mobile.html',  //生成的html存放路径,相对于 path
      template:'src/view/mobile.html',  //html模板路径
      inject:true,  //允许插件修改哪些内容,true/'head'/'body'/false,
      chunks:['vendors','mobile'],//加载指定模块中的文件,否则页面会加载所欲文件
      hash:false,  //为静态资源生成hash值
      minify:{  //压缩HTML文件
        removeComments:false,  //移除HTML中的注释
        collapseWhitespace:false  //删除空白符与换行符
      }
    }),
    /*作用:生成具有独立hash值的css和js文件,即css和js文件hash值解耦.
     *缺点:webpack-md5-hash插件对chunk-hash钩子进行捕获并重新计算chunkhash,它的计算方法是只计算模块本身的当前内容(包括同步模块)。这种计算方式把异步模块的内容忽略掉了,会造成一个问题:异步模块的修改并未影响主文件的hash值。
     */
    //new WebpackMd5Hash()
    new WebpackSplitHash()
  ]
}
 module.exports = config;

1. hash与chunkhash

首先大家先看一下法定文书档案对于互相的定义:

[hash] is replaced by the hash of the compilation.

hash表示的是compilation的hash值。

[chunkhash] is replaced by the hash of the chunk.

chunkhash意味着的是chunk的hash值。

chunkhash很好领悟,chunk在Webpack中的含义大家都理解,简单讲,chunk正是模块chunkhash也正是基于模块内容总结出的hash值。

那么该怎么领悟hash是compilation的hash值那句话呢?

率先先讲解一下Webpack中compilation的意义。

1. hash与chunkhash

首先我们先看一下合法文书档案对于互相的概念:

[hash] is replaced by the hash of the compilation.

hash意味着的是compilation的hash值。

[chunkhash] is replaced by the hash of the chunk.

chunkhash表示的是chunk的hash值。

chunkhash很好精通,chunk在Webpack中的含义大家都知晓,简单讲,chunk正是模块webpack学习笔记之优化缓存,以及js与css的hash指纹解耦方案。。chunkhash也正是依照模块内容总结出的hash值。

那正是说该如何掌握hash是compilation的hash值那句话呢?

率先先讲解一下Webpack中compilation的含义。

起步

在上马学习Webpack在此以前,请先确定保障卫安全装了Node.js,建议设置新型版的Node.js。然后就能够利用npm安装Webpack了。你能够将Webpack安装到全局,不过大家普通会把它安装到项目倚重中。

当今跻身项目目录,并采用npm init -y开始化贰个暗许的package.json。打开终端,键入命令:

//全局安装
npm install webpack --g
//安装到项目依赖中
npm install webpack --save-dev

安装好Webpack信赖后,新建3个webpack.config.js文件,用来布局webpack。可是在配备webpack从前,先安装webpack-dev-server:

//全局安装
npm install webpack-dev-server --g
//安装到项目依赖中
npm install webpack-dev-server --save-dev

一 、在支付条件中应用压缩文件

1.1 compilation

Webpack官方文书档案中How to write a
plugin章节有对compilation的详解。

A compilation object represents a single build of versioned assets.
While running Webpack development middleware, a new compilation will
be created each time a file change is detected, thus generating a new
set of compiled assets. A compilation surfaces information about the
present state of module resources, compiled assets, changed files, and
watched dependencies.

compilation对象表示有些版本的财富对应的编写翻译进度。当使用Webpack的development中间件时,每回检查和测试到花色文件有转移就会创制贰个compilation,进而能够针对改动生产全新的编译文件。compilation对象涵盖当前模块能源、待编写翻译文件、有变动的文本和监听信赖的具有音讯。

与compilation对应的有个compiler对象,通过对照,能够协理大家对compilation有更深刻的敞亮。

The compiler object represents the fully configured Webpack
environment. This object is built once upon starting Webpack, and is
configured with all operational settings including options, loaders,
and plugins.

compiler对象表示的是布署齐全的Webpack环境。
compiler对象只在Webpack运营时营造二次,由Webpack组合具有的布署项营造转变。

简易的讲,compiler对象表示的是不变的webpack环境,是针对性webpack的;而compilation对象针对的是时刻可变的系列文件,只要文件有变动,compilation就会被再一次创立。

驾驭了compilation之后,再回头看hash的定义:

[hash] is replaced by the hash of the compilation.

compilation在类型中其余3个文本改动后就会被重复创立,然后webpack统计新的compilation的hash值,那一个hash值正是hash

万一利用hash作为编写翻译输出文件的hash指纹的话,如下:

output: {
    filename: '[name].[hash:8].js',
    path: __dirname + '/built'
}

hash是compilation对象计算机技术钻探所得,而不是现实的种类文件计算机技术讨论所得。所以上述配置的编写翻译输出文件,全部的文件名都会使用同一的hash指纹。如下:
4858美高梅 2

如此那般拉动的题目是,三个js文件任何多个变更都会潜移默化此外四个公文的尾声文件名。上线后,别的五个文件的浏览器缓存也整整失效。这一定不是大家想要的结果。

这正是说怎样制止这一个题材吧?

答案就是chunkhash

根据chunkhash的概念知道,chunkhash是依照实际模块文件的内容计算机技术钻探所得的hash值,所以某些文件的变更只会影响它本人的hash指纹,不会影响别的文件。配置webpack的output如下:

output: {
    filename: '[name].[chunkhash:8].js',
    path: __dirname + '/built'
}

编写翻译输出的文书为:
4858美高梅 3

各种文件的hash指纹都分裂,上线后无更改的文书不会失去缓存。

说来说去,好像chunkhash能够完全代替hash,那么hash就毫无用处吧?

1.1 compilation

Webpack官方文书档案中How to write a
plugin章节有对compilation的详解。

A compilation object represents a single build of versioned assets.
While running Webpack development middleware, a new compilation will
be created each time a file change is detected, thus generating a new
set of compiled assets. A compilation surfaces information about the
present state of module resources, compiled assets, changed files, and
watched dependencies.

compilation对象表示有个别版本的财富对应的编写翻译进程。当使用Webpack的development中间件时,每趟检查和测试到花色文件有改动就会创设二个compilation,进而能够针对改动生产全新的编译文件。compilation对象涵盖当前模块财富、待编写翻译文件、有改变的文本和监听看重的具有消息。

与compilation对应的有个compiler对象,通过对照,能够补助我们对compilation有更深远的敞亮。

The compiler object represents the fully configured Webpack
environment. This object is built once upon starting Webpack, and is
configured with all operational settings including options, loaders,
and plugins.

compiler对象表示的是铺排齐全的Webpack环境。
compiler对象只在Webpack运行时塑造二遍,由Webpack组合具有的陈设项创设转变。

简简单单的讲,compiler对象表示的是不变的webpack环境,是本着webpack的;而compilation对象针对的是每天可变的品类文件,只要文件有改观,compilation就会被再一次创建。

领悟了compilation之后,再回头看hash的定义:

[hash] is replaced by the hash of the compilation.

compilation在档次中其它三个文书改动后就会被再一次成立,然后webpack计算新的compilation的hash值,那么些hash值就是hash

借使应用hash用作编写翻译输出文件的hash指纹的话,如下:

output: {
    filename: '[name].[hash:8].js',
    path: __dirname + '/built'
}

hash是compilation对象计算机技术切磋所得,而不是切实可行的品类文件计算机技术讨论所得。所以上述配置的编写翻译输出文件,全部的文件名都会动用同样的hash指纹。如下:
4858美高梅 4

如此那般拉动的题材是,多个js文件任何一个变更都会影响此外几个公文的末段文件名。上线后,其它多个文件的浏览器缓存也一切失效。那自然不是大家想要的结果。

那么如何制止这些标题呢?

答案正是chunkhash

根据chunkhash的定义知道,chunkhash是基于具人体模型块文件的始末计算机技术钻探所得的hash值,所以某些文件的改动只会潜移默化它本人的hash指纹,不会潜移默化别的文件。配置webpack的output如下:

output: {
    filename: '[name].[chunkhash:8].js',
    path: __dirname + '/built'
}

编写翻译输出的文件为:
4858美高梅 5

各类文件的hash指纹都分裂,上线后无更改的公文不会错过缓存。

说来说去,好像chunkhash能够完全代表hash,那么hash就不要用处呢?

webpack-dev-server

它将在localhost:8080初始八个express静态财富web服务器,并且会以监听方式自动运营webpack,在浏览器打开http://localhost:8080/或http://localhost:8080/webpack-dev-server/能够浏览项目中的页面和编写翻译后的财富输出,并且经过一个socket.io服务实时监听它们的变化并活动刷新页面。
在终点中实施

webpack-dev-server --inline --hot

当大家修改了模块的内容后,webpack-dev-server会自行执行打包(打包后的结果会缓存到内部存款和储蓄器中,所以无法在地方文件中见到打包后的文件)。

inline慎选为任何页面添加了”Live Reloading”功用,而hot选用开启了”Hot
Module
Reloading”功效,那样就会尝试注重载发生变化的机件,而不是成套页面。那样就达成了改动文件,界面就会自动更新了。
咱俩得以在package.json中输入以下内容:

"scripts": {
   "dev": "webpack-dev-server --colors --hot --inline",
   "build": "webpack --colors --watch"
},

诸如此类我们只须求键入npm run dev命令就能履行上面的命令了。

在那后边,先看看项指标结构以及三个简单的webpack config:

|——hello-webpack
   |——src  # 项目源码
      |——assets # 资源文件
         |——img # 图片
         |——css # 样式
      |——component  # 页面组件
      main.js  # 入口文件
   |——static # 静态资源文件
   index.html
   package.json
   webpack.config.js

webpack config.js

var path = require('path');
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
  entry: {
    app: './src/main.js'
  },
  output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: '',
    filename: '[name].js'
  },
  resolve: {
    extensions: ['', '.js', '.jsx', '.json'],
    alias: {
      'src': path.resolve(__dirname, './src'),
      'assets': path.resolve(__dirname, './src/assets'),
      'components': path.resolve(__dirname, './src/components')
    }
  },
  module: {
    loaders: [
      {
        test: /\.js|jsx?$/,
        exclude: /node_modules/,
        loader: 'babel',
        query: {
          presets: ['es2015', 'react']
        }
      },
      {
        test: /\.css$/,
        loader: 'style!css',
        exclude: /node_modules/
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url',
        query: {
          limit: 10000,
          name: path.join('static', 'img/[name].[hash:7].[ext]')
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      inject: true
    }),
  ]
}

例如ReactJS项目中为了不让 Webpack 去遍历 React JS
及其全体信赖,你能够在webpack.config.js中重写它的表现。

1.2 hash应用场景

接上文所述,webpack的hash字段是依据每一遍编写翻译compilation的剧情计算机技术琢磨所得,也足以领略为品种完全文件的hash值,而不是指向各样具体文件的。

webpack针对compilation提供了八个hash相关的生命周期钩子:before-hashafter-hash。源码如下:

this.applyPlugins("before-hash");
this.createHash();
this.applyPlugins("after-hash");

hash能够用作版本控制的一环,将其视作编写翻译输出文件夹的称呼统一保管,如下:

output: {
    filename: '/dest/[hash]/[name].js'
}

小编们不探讨那种措施的创设和功能,那只是hash的一种选用场景。当然,hash还有别的的使用场景,不过小编近年来未触及过,欢迎大家补充。

1.2 hash应用场景

接上文所述,webpack的hash字段是基于每一遍编译compilation的始末计算机技术斟酌所得,也能够领略为品种完全文件的hash值,而不是本着各类具体文件的。

webpack针对compilation提供了四个hash相关的生命周期钩子:before-hashafter-hash。源码如下:

this.applyPlugins("before-hash");
this.createHash();
this.applyPlugins("after-hash");

hash能够作为版本控制的一环,将其作为编写翻译输出文件夹的名目统管,如下:

output: {
    filename: '/dest/[hash]/[name].js'
}

小编们不钻探那种方式的客体和成效,那只是hash的一种选拔场景。当然,hash还有其余的施用场景,可是小编近年来未触及过,欢迎大家补充。

Webpack配置

webpack.config.js为Webpack的暗许配置,大家能够为付出条件和生产条件分别做分歧的配备。下边一一介绍各种配置的效应。

config.alias: 每当 “react” 在代码中被引入,它会使用压缩后的 React JS
文件。

2. js与css共用同样chunkhash的缓解方案

webpack的见解是把具备类型的文本都以js为汇集点,不扶助js文件以外的公文为编写翻译入口。所以只要大家要编写翻译style文件,唯一的情势是在js文件中引入style文件。如下:

import 'style/style.scss';

webpack私下认可将js/style文件统统一编写译到1个js文件中,能够重视extract-text-webpack-plugin将style文件单独编写翻译输出。从那一点能够见到,webpack将style文件视为js的一片段。

那样的情势下有个很惨重的标题,当大家盼望将css单独编写翻译输出并且打上hash指纹,依照前文所述的行使chunkhash配备输出文件名时,编写翻译的结果是js和css文件的hash指纹完全相同。不论是独立修改了js代码依旧style代码,编写翻译输出的js/css文件都会打上全新的相同的hash指纹。那种处境下我们无能为力有效的实行版本管理和布局上线。

怎么会生出那种题材吗?

2. js与css共用同一chunkhash的消除方案

webpack的见识是把拥有连串的公文都是js为汇集点,不援救js文件以外的文件为编写翻译入口。所以假若大家要编写翻译style文件,唯一的点子是在js文件中引入style文件。如下:

import 'style/style.scss';

webpack暗中认可将js/style文件统统编写翻译到三个js文件中,能够借助extract-text-webpack-plugin将style文件单独编写翻译输出。从那点能够看来,webpack将style文件视为js的一有的。

这么的格局下有个很要紧的难点,当我们期望将css单独编写翻译输出并且打上hash指纹,依照前文所述的施用chunkhash计划输出文件名时,编写翻译的结果是js和css文件的hash指纹完全相同。不论是单身修改了js代码依旧style代码,编写翻译输出的js/css文件都会打上全新的同样的hash指纹。那种光景下大家鞭长莫及有效的开展版本管理和布署上线。

缘何会爆发那种难点呢?

entry

entry是进口配置项,能够是string,Array抑或1个Object:

entry: {
  app: './src/main.js'
},

entry: './src/main.js'

设若页面有几个入口能够如此写:

entry: ['./src/home.js', '.src/profile.js']
//或
entry: {
  home: './src/home.js',
  profile: './src/profile.js'
}

noParse: 阻止Webpack 去分析相当压缩后的文本。

2.1 chunkhash的测算方式

前文提到了webpack的编写翻译理念,webpack将style视为js的一片段,所以在测算chunkhash时,会把持有的js代码和style代码混合在一起总结。比如main.js引用了main.scss:

import 'main.scss';
alert('I am main.js');

main.scss的剧情如下:

body{
    color: #000;
}

webpack计算chunkhash时,以main.js文本为编写翻译入口,整个chunk的内容会将main.scss的始末也算算在内:

body{
    color: #000;
}
alert('I am main.js');

故此,不论是修改了js代码还是scss代码,整个chunk的内容都转移了,计算机技术钻探所得的chunkhash自然就不一样了。

那正是说怎样消除那种难题呢?

2.1 chunkhash的盘算情势

前文提到了webpack的编写翻译理念,webpack将style视为js的一局地,所以在盘算chunkhash4858美高梅,时,会把拥有的js代码和style代码混合在联合总括。比如main.js引用了main.scss:

import 'main.scss';
alert('I am main.js');

main.scss的内容如下:

body{
    color: #000;
}

webpack计算chunkhash时,以main.js文件为编写翻译入口,整个chunk的始末会将main.scss的情节也算算在内:

body{
    color: #000;
}
alert('I am main.js');

故而,不论是修改了js代码照旧scss代码,整个chunk的始末都改变了,计算机技术钻探所得的chunkhash自然就分化了。

这正是说什么样缓解那种题材吗?

output

output是出口配置。

output: {
  path: path.resolve(__dirname, './dist'),
  publicPath: '/',
  filename: '[name].js',
  chunkFilename: '[id].[hash].js'
}

path是文本输出到地面包车型大巴门路,publicPath是文件的引用路径,可用来被有些Webpack插件用来处理CSS,HTML文件中的U库罗德L,一般用于生产格局,filename是包裹后的入口文件名,chunkFilename是每一种模块编写翻译后的文书名,在这之中[hash]是用来唯一标识文件,重要用来防备缓存。

当加载七个压缩文件时,下述方法更优雅轻便,webpack.production.js:

2.2 contenthash

前文提到了使用extract-text-webpack-plugin单独编写翻译输出css文件,造成上一节js/css共用hash指纹的安插为:

new ExtractTextPlugin('[name].[chunkhash].css');

extract-text-webpack-plugin提供了别的一种hash值:contenthash。顾名思义,contenthash表示的是文件文件内容的hash值,约等于只有style文件的hash值。这些hash值正是化解上述难点的银弹。修改配置如下:

new ExtractTextPlugin('[name].[contenthash].css');

编写翻译输出的js和css文件将会有其独自的hash指纹。

到此地是否就找到完美的消除方案了啊?

远远没有!

构成上文提到的各类,考虑一下那个问题:一经只修改了main.scss文件,未修改main.js文件,那么编写翻译输出的js文件的hash指纹会变动呢?

答案是肯定的。

修改了main.scss编写翻译输出的css文件hash指纹理所当然要革新,可是大家从没修改main.js,可是js文件的hash指纹也更新了。那是因为上文提到的:

webpack计算chunkhash时,以main.js文本为编写翻译入口,整个chunk的内容会将main.scss的始末也算算在内。

那么怎么化解这几个标题吗?

很简短,既然大家精晓了webpack总结chunkhash的方法,这大家就从这点出发,尝试修改chunkhash的总计方法。

2.2 contenthash

前文提到了使用extract-text-webpack-plugin单独编写翻译输出css文件,造成上一节js/css共用hash指纹的布局为:

new ExtractTextPlugin('[name].[chunkhash].css');

extract-text-webpack-plugin提供了其它一种hash值:contenthash。顾名思义,contenthash意味着的是文件文件内容的hash值,也等于唯有style文件的hash值。那一个hash值正是消除上述难题的银弹。修改配置如下:

new ExtractTextPlugin('[name].[contenthash].css');

编写翻译输出的js和css文件将会有其独立的hash指纹。

到那边是还是不是就找到完美的化解方案了吧?

不以万里为远没有!

结缘上文提到的各种,考虑一下那么些难点:一旦只修改了main.scss文件,未修改main.js文件,那么编写翻译输出的js文件的hash指纹会改变啊?

答案是大势所趋的。

修改了main.scss编写翻译输出的css文件hash指纹理所当然要立异,但是大家没有修改main.js,不过js文件的hash指纹也换代了。那是因为上文提到的:

webpack计算chunkhash时,以main.js文件为编写翻译入口,整个chunk的始末会将main.scss的情节也算算在内。

那么怎么消除这些标题啊?

很简短,既然大家驾驭了webpack计算chunkhash的措施,这我们就从这点出发,尝试修改chunkhash的推断方法。

path

可是用来告诉Webpack在哪个地方存放结果文件,上边例子中,最后的打包文件会放到与当前剧本文件同级目录的dist目录下。即:

hello-webpack
  +dist
  -webpack.config.js
var webpack = require("webpack");
...
var HtmlWebpackPlugin = require('html-webpack-plugin');

var deps = [
  'react/dist/react.min.js',
  'react-dom/dist/react-dom.min.js'
];
var config = {
  ...
  resolve:{
    alias:{},
    fallback:path.join(__dirname, "node_modules")
  },
  ...
  module:{
     ...
     noParse:[]  
   }  
}
/*当加载多个压缩文件时,下述方法更优雅简便*/
deps.forEach(function(dep){  
  var depPath = path.resolve(node_modules, dep);
  //path.dep是路径分隔符。
  config.resolve.alias[dep.split(path.dep)[0]] = depPath;  
  config.module.noParse.push(depPath);

});

module.exports = config;

2.3 chunk-hash

此小节内容只适用于webpack1,webpack2已经修复了hash相关的一个钱打二十七个结规则。

chunk-hash并不是webpack中另一种hash值,而是compilation执行生命周期中的1个钩子。chunk-hash钩子代表的是哪位阶段呢?请看webpack的Compilation.js源码中以下一些:

for(i = 0; i < chunks.length; i++) {
        chunk = chunks[i];
        var chunkHash = require("crypto").createHash(hashFunction);
        if(outputOptions.hashSalt)
            hash.update(outputOptions.hashSalt);
        chunk.updateHash(chunkHash);
        if(chunk.entry) {
            this.mainTemplate.updateHashForChunk(chunkHash, chunk);
        } else {
            this.chunkTemplate.updateHashForChunk(chunkHash);
        }
        this.applyPlugins("chunk-hash", chunk, chunkHash);
        chunk.hash = chunkHash.digest(hashDigest);
        hash.update(chunk.hash);
        chunk.renderedHash = chunk.hash.substr(0, hashDigestLength);
}

webpack使用NodeJS内置的crypto模块计算chunkhash,具体采纳哪类算法与大家谈谈的始末无关,咱们只必要关爱上述代码中this.applyPlugins("chunk-hash", chunk, chunkHash);的进行时机。

chunk-hash是在chunhash总计停止之后执行的,那就象征若是我们在chunk-hash钩子中能够用新的chunkhash替换已存在的值。如下伪代码:

compilation.plugin("chunk-hash", function(chunk, chunkHash) {
        var new_hash = md5(chunk);
        chunkHash.digest = function () {
        return new_hash;
    };
});

webpack之所以假诺流行的案由之一便是有所庞大的社区和多重的开发者们,实际上,我们相遇的标题早已有前任帮大家缓解了。插件webpack-md5-hash就是上述伪代码的有血有肉得以实现,大家需求做的只是将那一个插件加入到webpack的布局中:

var WebpackMd5Hash = require('webpack-md5-hash');

module.exports = {
    output: {
        //...
        chunkFilename: "[chunkhash].chunk.js"
    },
    plugins: [
        new WebpackMd5Hash()
    ]
};

2.3 chunk-hash

此小节内容只适用于webpack1,webpack2已经修复了hash相关的测算规则。

chunk-hash并不是webpack中另一种hash值,而是compilation执行生命周期中的多个钩子。chunk-hash钩子代表的是哪个阶段呢?请看webpack的Compilation.js源码中以下部分:

for(i = 0; i < chunks.length; i++) {
        chunk = chunks[i];
        var chunkHash = require("crypto").createHash(hashFunction);
        if(outputOptions.hashSalt)
            hash.update(outputOptions.hashSalt);
        chunk.updateHash(chunkHash);
        if(chunk.entry) {
            this.mainTemplate.updateHashForChunk(chunkHash, chunk);
        } else {
            this.chunkTemplate.updateHashForChunk(chunkHash);
        }
        this.applyPlugins("chunk-hash", chunk, chunkHash);
        chunk.hash = chunkHash.digest(hashDigest);
        hash.update(chunk.hash);
        chunk.renderedHash = chunk.hash.substr(0, hashDigestLength);
}

webpack使用NodeJS内置的crypto模块总结chunkhash,具体选拔哪类算法与大家钻探的剧情无关,大家只供给关怀上述代码中this.applyPlugins("chunk-hash", chunk, chunkHash);的实践时机。

chunk-hash是在chunhash总结停止之后执行的,那就表示假如我们在chunk-hash钩子中得以用新的chunkhash替换已存在的值。如下伪代码:

compilation.plugin("chunk-hash", function(chunk, chunkHash) {
        var new_hash = md5(chunk);
        chunkHash.digest = function () {
        return new_hash;
    };
});

webpack之所以要是流行的原因之一正是颇具巨大的社区和一类别的开发者们,实际上,大家相见的难题一度有前人帮我们化解了。插件webpack-md5-hash就是上述伪代码的求实贯彻,大家须要做的只是将以此插件出席到webpack的安插中:

var WebpackMd5Hash = require('webpack-md5-hash');

module.exports = {
    output: {
        //...
        chunkFilename: "[chunkhash].chunk.js"
    },
    plugins: [
        new WebpackMd5Hash()
    ]
};
filename

进口文件打包后的名目,[name]对应着入口文件的key值,例如:app.js,那对多入口文件是很有用的,应为入口文件能够有多个,然则filename只好有二个,所以对于地方的多入口,最终便是:home.js,’profile.js’,当然为了反映文件层级关系得以这么写:

filename: 'js/[name].js'

终极的结果正是:

|——hello-webpack
   |——dist
   |——js
       home.js
       profile.js
    webpack.config.js

② 、分离应用和第3方文件

3. 结语

静态能源的版本管理是前者工程化中十一分关键的一环,使用webpack作为构建工具时要求小心翼翼采取hash
chunkhash,并且还索要专注webpack将全部正是js模块那种看法带来的有些困难。

webpack能够说是现阶段最盛行的塑造筑工程具了,然而其官方文书档案太过笼统,许多细节尚未列出,须要切磋源码才会精晓。万幸大家决不单独应战,庞大的社区财富也是有助于webpack流行的严重性成分之一。

编写至此,常规的前端项目中关于静态财富hash指纹的题材基本得到了消除,不过前端的条件是扑朔迷离的,各类新技巧新框架见惯司空。最终留一点悬念给大家:像vue那种将template/js/style统统写在三个js文件中,如何确认保障在只修改了style时不影响编写翻译输出的js文件hash指纹?

3. 结语

静态财富的版本管理是前者工程化中国和亚洲常关键的一环,使用webpack作为创设筑工程具时要求谨慎接纳hash
chunkhash,并且还须求注意webpack将总体就是js模块这种看法带来的有些劳苦。

webpack能够说是近期最流行的营造筑工程具了,可是其法定文档太过笼统,许多细节尚未列出,需求商量源码才会询问。辛亏大家不要独自作战,庞大的社区财富也是拉动webpack流行的主要性成分之一。

撰写至此,常规的前端项目中关于静态财富hash指纹的难点着力得到了化解,不过前端的条件是犬牙相制的,种种新技巧新框架不乏先例。最终留一点悬念给大家:像vue那种将template/js/style统统写在一个js文件中,怎么样保管在只修改了style时不影响编写翻译输出的js文件hash指纹?

chunkFilename

即非入口文件打包后的名目,未被列在entry中,却又要求被打包出来的文件命名配置。一般情状下是不须求以此布局的。比如大家在做异步加载模块时就须要接纳了:

Vue.component('async-webpack-example', function (resolve) {
  // 这个特殊的 require 语法告诉 webpack
  // 自动将编译后的代码分割成不同的块,
  // 这些块将通过 Ajax 请求自动下载。
  require(['./my-async-component'], resolve)
})

当你的运用信赖其余库尤其是像 React JS
那种大型库的时候,须要考虑把那些重视分离出来,那样就可见让用户在您更新应用之后不须要再行下载第3方文件。上述配置文件中的entry里添加了第1方包vendors,其值为要分开打包的文本。运营配置后会在dist/js下生成五个单身文件:app.js、mobile.js、vendors.js。注意在页面中药引入vendors.js

publicPath

文本的引用路径,可用来被部分Webpack插件用来处理CSS,HTML文件中的U昂科拉L,在付出情势下建议使用相对路径,在生养格局中,如若您的能源文件放在其余服务器上,能够动用服务器的地址。当然你也足以毫不配置publicPath,。
在项目中作者动用了url-loader加载图片,

{
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
    loader: 'url',
    query: {
      limit: 10000,
      name: path.join('static', 'img/[name].[hash:7].[ext]') # 图片最终的输出路径
    }
}

main.js中央银行使了图片

import Girl from 'assets/img/girl.jpg'

那就是说最终浏览器访问的图纸路径正是:

static/img/girl.7672e53.jpg 

所以能够依据开发环境和生育环境铺排分裂的publicPath
在生产条件中,由于自个儿的财富文件放在项目目录下,所以能够那样布置output:

output: {
  path: path.resolve(__dirname, './dist'),
  publicPath: './',
  filename: 'js/[name].[chunkhash].js',
  chunkFilename: `js/[id].[chunkhash].js`
}

那正是说最终包装后的输出目录结构正是如此的:

|——dist
   |——static
      |——img
         girl.7672e53.jpg
      |——js
         app.js
    index.html

所以通过static/img/girl.7672e53.jpg能够访问到图片。在开发条件下,经过测试,将publicPath安装为’./’界面是心有余而力不足加载出来的,所以在支付条件下得以不用安装。

<script src="../dist/vendors.js"></script>
<script src="../dist/app.js"></script>

loader

module: {
    loaders: [
      {
        test: /\.js|jsx?$/,
        exclude: /node_modules/,
        loader: 'babel',
        query: {
          presets: ['es2015', 'react']
        }
      },
      {
        test: /\.css$/,
        loader: 'style!css',
        exclude: /node_modules/
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url',
        query: {
          limit: 10000,
          name: path.join('static', 'img/[name].[hash:7].[ext]')
        }
      }
    ]
  }

由于Webpack本身只好处理JavaScript
模块,假若要拍卖其余门类的文本,就必要动用loader
举办转移。Loader能够领悟为是模块和能源的转换器,它自个儿是3个函数,接受源文件作为参数,再次来到转换的结果。不相同的loader能够将各体系型的文本转换为浏览器能够接受的格式如JS,Stylesheets等等。

下边一一对那么些子参数进行认证:

  • test参数用来提醒当前配备项针对如何能源,当参数匹配时,就会采用相应的loader。
  • exclude参数用来剔除掉须求忽略的能源。
  • include参数用来代表本loader配置仅针对如何目录/文件,从名称上就足以认为跟exclude意义反倒。
  • loader/loaders参数,用来提示用哪些/哪些loader来处理目的能源,那俩表明的实际是二个意味,只是写法不平等,小编个人喜欢将loader写成一行,多少个loader间使用!分割,那种样式类似于管道的概念,例如loader: 'css?!postcss!less',能够很显明地看出,指标能源先经less-loader处理今后将结果提交postcss-loader作进一步处理,然后最终再提交css-loader。

loader自身也是足以布置的,传入分歧的参数能够达成分化的职能。以url-loader为例,大家安顿url-loader使小于10000字节的图形应用DataU奥迪Q5L,大于一千0字节的图形应用U卡宴L,name质量配置输出图片的图纸名称,例如:

require('a.png') => static/a.3445645.png

今非昔比的loader配置参数不平等,具体计划参数能够去官网查阅。

③ 、多重入口

loader链

五个loader可以链式调用,成效于同一种文件类型。工作链的调用顺序是从右向左,各样loader之间使用”!”分开。
以处理css文件为例,我们必要css-loader来拍卖css文件,然后利用style-loader将css样式插入到html的style标签中。

当使用有多个页面,
页面之间固然有共享代码,不过不想在页面中加载全部代码时方可定义多重入口。例如配置文件中的app.js针对pc端页面,mobile.js仅针对移动端页面,output的filename:’js/[name].[chunkhash:8].js’,采纳了文本名变量,那样在dist/js中可转移与源文件同名的文本。

plugin

插件能够成功越来越多loader不可能势如破竹的意义。
插件的使用相似是在webpack的布署消息plugins选项中钦赐。
loader是在包装前或包装的进程中效果于单个文件。plugin平日在卷入进程截止后,功用于包依然chunk级别。

以下是局地常用的插件:

  1. extract-text-webpack-plugin

ExtractTextPlugin的效应是把各类chunk加载的css代码合并成一个css文件并在页面加载的时候以<link>的花样实行加载。

var ExtractTextPlugin = require('extract-text-webpack-plugin')

module: {
  loaders: [
    {
      test: /\.css$/, 
      loader:ExtractTextPlugin.extract("style-loader","css-loader") }
  ]
},
plugins: [
  new ExtractTextPlugin(path.join('static', 'css/[name].[contenthash].css'))
]

留意:倘若想要把CSS放到HTML的style标签中,可以不使用extract-text-webpack-plugin,只要用css-loader和style-loader就足以了。

  1. html-webpack-plugin

html-webpack-plugin,是用来生产html的,当中filename是生产的公文路径和名称,template是利用的模板,inject是指将js放在body依然head里。为true会将js放到body里

  new HtmlWebpackPlugin({
    filename: 'index.html',
    template: 'index.html',
    inject: true
  }),

这么些插件是提出一定要安装的。

  1. uglifyJSPlugin

    uglifyJSPlugin是将代码实行削减的。

    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
    
  2. CommonsChunkPlugin

CommonsChunkPlugin是将多个入口文件之间共享的块打包成七个单身的js文件。至此,你只供给在各类页面都加载这么些集体代码的js文件,就能够既保证代码的完整性,又不会重复下载公共代码了。

new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor',
    filename: '[name].[chunkhash].js',
    minChunks: 4
  })

* `name`,给这个包含公共代码的chunk命个名(唯一标识)。
* `filename`,如何命名打包后生产的js文件。
* `minChunks`,公共代码的判断标准:某个js模块被多少个chunk加载了才算是公共代码。
* `chunks`,表示需要在哪些chunk(也可以理解为webpack配置中entry的每一项)里寻找公共代码进行打包。不设置此参数则默认提取范围为所有的chunk。

四 、优化缓存及懒加载

resolve

  resolve: {
    extensions: ['', '.js', '.jsx', '.json'],
    alias: {
      'src': path.resolve(__dirname, './src'),
      'assets': path.resolve(__dirname, './src/assets'),
      'components': path.resolve(__dirname, './src/components')
    }
  }

resolve.extensions是对模块后缀名的简写,配置后,原本是require('./components/app.jsx')
能够简写为require('./components/app')

resolve.alias是小名,配置后,比如原本是require('./src/components/nav.jsx')能够简写为require('components/nav.jsx')

在生养条件中,将出口文件名添加hash值,目标是在文件更改时强制客户端重新加载那一个文件,而未变更的文件三番五次选用缓存文件。常用的有hash和chunkhash。配置文件中的[chunkhash:8]即截取8位chunkhash值。  

Webpack中的hash与chunkhash

webpack的编写翻译理念:webpack将style视为js的一局地,所以在估测计算chunkhash时,会把装有的js代码和style代码混合在一块儿总括。比如entry.js引用了main.css:

hash与chunkhash

遵守合法的定义hash纵然webpack的每1遍编写翻译(compilation)所发生的hash值,chunkhash从字面上明白正是每2个chunk的hash值。那么哪些时候会爆发编写翻译以及chunk又是怎么事物?

import 'main.css'; 
alert('I am main.js');

compilation

compilation指标表示有些版本的财富对应的编译进程。当使用Webpack的development中间件时,每回检查和测试到项目文件有改观就会创立二个compilation,进而能够针对改动生产全新的编写翻译文件。以及在每便执行webpack命令时都会创设1个compilation。也正是说当成立了二个compilation,大家有着须求打包的文书(js,css,img,font等)都会发出相同的hash。

若是在品种中我们运用hash作为编写翻译输出文件的hash的话,像这么:

entry: {
    home: './src/home.js',
    profile: './src/profile.js'
},
output: {
    path: './dist',
    filename: 'js/[name].[hash].js'
}

那正是说在编写翻译后有所的文件名都会接纳同样的hash值,那样拉动的难题是,下面八个js文件任何三个改动都会影响此外文件的末段文件名。上线后,别的文件的浏览器缓存也一切失效。那必然不是咱们想要的结果。

那么怎样制止那样的题材吗?

答案就是运用chunkhash
按部就班上面所说,chunkhash是各类chunk的hash值,chunk纵使模块(webpack中整整皆模块),chunkhash也正是依照模块内容总计出的hash值。所以有些文件的改变只会影响它自身的hash值,不会潜移默化别的文件。
故此可以将方面包车型客车filename改为:

 filename: 'js/[name].[chunkhash].js'

那样的话每种文件的hash值都不平等,上线后无更改的公文不会失掉缓存。

但是使用chunkhash也不可能缓解全数标题,比如打包css文件。

webpack总结chunkhash时,以entry.js文件为编写翻译入口,整个chunk的内容会将main.css的剧情也算算在内。就此,不论是修改了js代码依旧css代码,整个chunk的剧情都改成了,计算所得的chunkhash随之改变。但美好图景下是想css或js内容变更时仅影响自身文件的chunkhash,那样客户端只需创新一部分文书。消除此题材首先要将css单独编写翻译输出文件,因为健康状态下webpack会把js文件中引入的css文件编写翻译输出到html页面包车型地铁<style></style>标签中。

js与css共用同一chunkhash的缓解方案

前文提到了webpack的编写翻译理念,webpack将style视为js的一部分,所以在测算chunkhash时,会把拥有的js代码和style代码混合在一齐计算。所以,不论是修改了js代码依旧css代码,整个chunk的始末都改变了,计算机技术商讨所得的chunkhash自然就同一了。

那正是说什么样缓解那种题材吗?

1.使用extract-text-webpack-plugin单独编写翻译输出css文件

contenthash

webpack暗许将js/style文件统统一编写译到一个js文件中,能够信赖extract-text-webpack-plugin将style文件单独编写翻译输出。所以我们能够如此布置:

new ExtractTextPlugin('./dist/css/[name].[contenthash].css')

contenthash意味着的是文本文件内容的hash值,也正是唯有style文件的hash值。那样编译输出的js和css文件将会有其独立的hash值。

安装extract-text-webpack-plugin,

以身作则代码

在看文章的同时,搭配以身作则项目会更直观哦,赶紧动起手来,开端入坑Webpack吧:)。

克隆后,请执行 npm install

//启动运行环境
npm run dev 
//执行打包
npm run build
npm install extract-text-webpack-plugin --save-dev

下一场在安排文件中引入插件,

//webpack.production.config.js
var ExtractTextPlugin = require('extract-text-webpack-plugin');

该插件除了chunkhash还提供了其余一种hash值contenthash。顾名思义,contenthash代表的是文件文件内容的hash值,也正是唯有style文件的hash值。此hash是可缓解上述难题的关键所在。上述配置文件使用了contenthash:

//webpack.production.config.js
new ExtractTextPlugin("css/[name].[contenthash:8].css"),//设置其路径(路径相对于path)

2.利用应用webpack-md5-hash解耦css和js文件hash值

再考虑以下景况,只修改了main.css文件,未修改entry.js文件,编写翻译输出的js文件hash是还是不是变动?答案是改变了,因为上文提到的webpack的编写翻译理念,webpack将style视为js的一局地,所以在测算chunkhash时,会把全数的js代码和style代码混合在一块儿总结。化解办法是利用webpack-md5-hash插件:

//webpack.production.config.js

var WebpackMd5Hash = require('webpack-md5-hash');
...
new WebpackMd5Hash();

它的效能是生成具有独立hash值的css和js文件,即css和js文件hash值解耦。webpack-md5-hash插件对chunk-hash钩子进行捕获并再一次总计chunkhash,它的计量格局是只总结模块自身的最近内容(包含联合模块)。

3.主文件使用hash代替chunkhash消除异步加载模块改变时主文件hash不转移

假设文件中引入了异步模块,异步模块修改后会影响编写翻译输出的js文件的chunkhash吗?未来进口文件中引入异步模块a.js,a.js文件又异步引入b.js,b.js同步引入c模块

//entry.js

'use strict';

import './saveCarInfo.js';

window.onload = function(){//懒加载
  require.ensure(['./a.js'],function(require){
    var moduleA = require('./a.js');
  },'a');
};



//a.js

'use strict'

console.log("a");

setTimeout(function(){
  require.ensure([],function(require){
    require('./b.js');
  },'b');
},10000);

module.exports = "moduleA";


//b.js
import fn_c from './c.js';

console.log('b');
module.exports = 'moduleB';


//c.js

console.log("c");
module.exports = "moduleC";

运作npm run
deploy,编写翻译输出如下,我们看到除了进口文件、css文件、html文件被输出外,异步加载的模块a.js、b.js也被视作独立模块输出。

4858美高梅 6

此时修改a.js文件中的代码,经编写翻译后,a.[chunkhash].js的chunkhash会改变,而转变的主文件app.[chunkhash].js的chunkhash值并没有变动。原因是webpack-md5-hash的那种总计办法把异步模块的情节忽略掉了,这会造成一个难点:异步模块的改动并未影响主文件的chunkhash值。消除办法是将出口的主文件选取[hash],而非[chunkhash]

output:{
  path:path.resolve(__dirname, 'dist'),
  publicPath:'../',//生成的html里的引用路径用 publicPath
  filename: 'js/[name].[hash:8].js',
  //异步加载的模块是要以文件形式加载,生成的文件名是以chunkFilename配置的
  chunkFilename: 'js/[name].[chunkhash:8].js'
},

这种做法也存在缺点,假如项目中设有不止3个主js文件,修改任意js代码会潜移默化全部最后主文件的[hash]值。例如地方的品种布局中会生成四个带[hash]的主文件:app.[hash].js,
mobile.[hash].js。无论是修改entry.js代码依然异步模块a.js,或b.js的代码,app.[hash].js和mobile.[hash].js的[hash]都会变动。

补给:npm提供了webpack-split-hash插件代替webpack-md5-hash,该插件能够取得到各异步模块的hash值,然后将那么些hash值与主文件的代码内容一同看做总括hash的参数,那样就能保险主文件的hash值会跟随异步模块的改动而修改。但自笔者表达后没有达成。。。

4.行使html-webpack-plugin动态生成html

安顿文件中的输出文件都带了[chunkhash]用作版本号,在style或js文件改变时,其值都会随着转移。利用html-webpack-plugin在webpack达成前端资源打包今后,自动将打包后的财富路径和版本号写入HTML中,达到自动化的功能。

//webpack.production.config.js

var HtmlWebpackPlugin = require('html-webpack-plugin');
...
var config = {
...
plugins:[
... 
  new HtmlWebpackPlugin({
    filename:'view/index.html',  //生成的html存放路径,相对于 path
    template:'src/view/index.html',  //html模板路径
    inject:true,  //允许插件修改哪些内容,true/'head'/'body'/false,
    chunks:['vendors','app'],//加载指定模块中的文件,否则页面会加载所有文件
    hash:false,  //为静态资源生成hash值
    minify:{  //压缩HTML文件
      removeComments:false,  //移除HTML中的注释
      collapseWhitespace:false  //删除空白符与换行符
     }    
  }),
  new HtmlWebpackPlugin({
    filename:'view/mobile.html',  //生成的html存放路径,相对于 path
    template:'src/view/mobile.html',  //html模板路径
    inject:true,  //允许插件修改哪些内容,true/'head'/'body'/false,
    chunks:['vendors','mobile'],//加载指定模块中的文件,否则页面会加载所有文件
    hash:false,  //为静态资源生成hash值
    minify:{  //压缩HTML文件
      removeComments:false,  //移除HTML中的注释
      collapseWhitespace:false  //删除空白符与换行符
     }    
  })
]}

如上正是本文的全体内容,希望对大家的学习抱有帮忙,也指望大家多多帮助脚本之家。

您只怕感兴趣的篇章:

  • webpack进阶——缓存与独立包装的用法
  • webpack独立包装和缓存处理详解
  • Vue.js中用webpack合并打包八个零件并贯彻按需加载

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图
Copyright @ 2010-2019 美高梅手机版4858 版权所有