深入webpack打包原理及l(fā)oader和plugin的實(shí)現(xiàn)
本文討論的核心內(nèi)容如下:
webpack
進(jìn)行打包的基本原理- 如何自己實(shí)現(xiàn)一個
loader
和plugin
注: 本文使用的 webpack
版本是 v4.43.0
, webpack-cli
版本是 v3.3.11
, node
版本是 v12.14.1
, npm
版本 v6.13.4
(如果你喜歡 yarn
也是可以的),演示用的 chrome
瀏覽器版本 81.0.4044.129(正式版本) (64 位)
1. webpack打包基本原理
webpack的一個核心功能就是把我們寫的模塊化的代碼,打包之后,生成可以在瀏覽器中運(yùn)行的代碼,我們這里也是從簡單開始,一步步探索webpack的打包原理
1.1 一個簡單的需求
我們首先建立一個空的項(xiàng)目,使用 npm init -y
快速初始化一個 package.json
,然后安裝 webpack webpack-cli
接下來,在根目錄下創(chuàng)建 src
目錄, src
目錄下創(chuàng)建 index.js
, add.js
, minus.js
,根目錄下創(chuàng)建 index.html
,其中 index.html
引入 index.js
,在 index.js
引入 add.js
, minus.js
,
目錄結(jié)構(gòu)如下:
文件內(nèi)容如下:
// add.js export default (a, b) => { return a + b } // minus.js export const minus = (a, b) => { return a - b } // index.js import add from './add.js' import { minus } from './minus.js' const sum = add(1, 2) const division = minus(2, 1) console.log('sum>>>>>', sum) console.log('division>>>>>', division)
<!--index.html--> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>demo</title> </head> <body> <script src="./src/index.js"></script> </body> </html>
這樣直接在 index.html
引入 index.js
的代碼,在瀏覽器中顯然是不能運(yùn)行的,你會看到這樣的錯誤
Uncaught SyntaxError: Cannot use import statement outside a module
是的,我們不能在 script
引入的 js
文件里,使用 es6
模塊化語法
1.2 實(shí)現(xiàn)webpack打包核心功能
我們首先在項(xiàng)目根目錄下再建立一個bundle.js,這個文件用來對我們剛剛寫的模塊化 js
代碼文件進(jìn)行打包
我們首先來看webpack官網(wǎng)對于其打包流程的描述:
it internally builds a dependency graph which maps every module your project needs and generates one or more bundles(webpack會在內(nèi)部構(gòu)建一個 依賴圖(dependency graph),此依賴圖會映射項(xiàng)目所需的每個模塊,并生成一個或多個 bundle)
在正式開始之前,結(jié)合上面 webpack
官網(wǎng)說明進(jìn)行分析,明確我們進(jìn)行打包工作的基本流程如下:
首先,我們需要讀到入口文件里的內(nèi)容(也就是index.js的內(nèi)容) 其次,分析入口文件,遞歸的去讀取模塊所依賴的文件內(nèi)容,生成依賴圖 最后,根據(jù)依賴圖,生成瀏覽器能夠運(yùn)行的最終代碼 1. 處理單個模塊(以入口為例) 1.1 獲取模塊內(nèi)容
既然要讀取文件內(nèi)容,我們需要用到 node.js
的核心模塊 fs
,我們首先來看讀到的內(nèi)容是什么:
// bundle.js const fs = require('fs') const getModuleInfo = file => { const body = fs.readFileSync(file, 'utf-8') console.log(body) } getModuleInfo('./src/index.js')
我們定義了一個方法 getModuleInfo
,這個方法里我們讀出文件內(nèi)容,打印出來,輸出的結(jié)果如下圖:
我們可以看到,入口文件 index.js
的所有內(nèi)容都以字符串形式輸出了,我們接下來可以用正則表達(dá)式或者其它一些方法,從中提取到 import
以及 export
的內(nèi)容以及相應(yīng)的路徑文件名,來對入口文件內(nèi)容進(jìn)行分析,獲取有用的信息。但是如果 import
和 export
的內(nèi)容非常多,這會是一個很麻煩的過程,這里我們借助 babel
提供的功能,來完成入口文件的分析
1.2 分析模塊內(nèi)容
我們安裝 @babel/parser
,演示時安裝的版本號為 ^7.9.6
這個babel模塊的作用,就是把我們js文件的代碼內(nèi)容,轉(zhuǎn)換成js對象的形式,這種形式的js對象,稱做 抽象語法樹(Abstract Syntax Tree, 以下簡稱AST)
// bundle.js const fs = require('fs') const parser = require('@babel/parser') const getModuleInfo = file => { const body = fs.readFileSync(file, 'utf-8') const ast = parser.parse(body, { // 表示我們要解析的是es6模塊 sourceType: 'module' }) console.log(ast) console.log(ast.program.body) } getModuleInfo('./src/index.js')
使用 @babel/parser
的 parse
方法把入口文件轉(zhuǎn)化稱為了 AST
,我們打印出了 ast
,注意文件內(nèi)容是在 ast.program.body
中,如下圖所示:
入口文件內(nèi)容被放到一個數(shù)組中,總共有六個 Node
節(jié)點(diǎn),我們可以看到,每個節(jié)點(diǎn)有一個 type
屬性,其中前兩個的 type
屬性是 ImportDeclaration
,這對應(yīng)了我們?nèi)肟谖募膬蓷l import
語句,并且,每一個 type
屬性是 ImportDeclaration
的節(jié)點(diǎn),其 source.value
屬性是引入這個模塊的相對路徑,這樣我們就得到了入口文件中對打包有用的重要信息了。
接下來要對得到的ast做處理,返回一份結(jié)構(gòu)化的數(shù)據(jù),方便后續(xù)使用。
1.3 對模塊內(nèi)容做處理
對 ast.program.body
部分?jǐn)?shù)據(jù)的獲取和處理,本質(zhì)上就是對這個數(shù)組的遍歷,在循環(huán)中做數(shù)據(jù)處理,這里同樣引入一個babel的模塊 @babel/traverse
來完成這項(xiàng)工作。
安裝 @babel/traverse
,演示時安裝的版本號為 ^7.9.6
const fs = require('fs') const path = require('path') const parser = require('@babel/parser') const traverse = require('@babel/traverse').default const getModuleInfo = file => { const body = fs.readFileSync(file, 'utf-8') const ast = parser.parse(body, { sourceType: 'module' }) const deps = {} traverse(ast, { ImportDeclaration({ node }) { const dirname = path.dirname(file); const absPath = './' + path.join(dirname, node.source.value) deps[node.source.value] = absPath } }) console.log(deps) } getModuleInfo('./src/index.js')
創(chuàng)建一個對象 deps
,用來收集模塊自身引入的依賴,使用 traverse
遍歷 ast
,我們只需要對 ImportDeclaration
的節(jié)點(diǎn)做處理,注意我們做的處理實(shí)際上就是把相對路徑轉(zhuǎn)化為絕對路徑,這里我使用的是 Mac
系統(tǒng),如果是 windows
系統(tǒng),注意斜杠的區(qū)別
獲取依賴之后,我們需要對 ast
做語法轉(zhuǎn)換,把 es6
的語法轉(zhuǎn)化為 es5
的語法,使用 babel
核心模塊 @babel/core
以及 @babel/preset-env
完成
安裝 @babel/core @babel/preset-env
,演示時安裝的版本號均為 ^7.9.6
const fs = require('fs') const path = require('path') const parser = require('@babel/parser') const traverse = require('@babel/traverse').default const babel = require('@babel/core') const getModuleInfo = file => { const body = fs.readFileSync(file, 'utf-8') const ast = parser.parse(body, { sourceType: 'module' }) const deps = {} traverse(ast, { ImportDeclaration({ node }) { const dirname = path.dirname(file); const absPath = './' + path.join(dirname, node.source.value) deps[node.source.value] = absPath } }) const { code } = babel.transformFromAst(ast, null, { presets: ["@babel/preset-env"] }) const moduleInfo = { file, deps, code } console.log(moduleInfo) return moduleInfo } getModuleInfo('./src/index.js')
如下圖所示,我們最終把一個模塊的代碼,轉(zhuǎn)化為一個對象形式的信息,這個對象包含文件的絕對路徑,文件所依賴模塊的信息,以及模塊內(nèi)部經(jīng)過 babel
轉(zhuǎn)化后的代碼
2. 遞歸的獲取所有模塊的信息
這個過程,也就是獲取 依賴圖(dependency graph)
的過程,這個過程就是從入口模塊開始,對每個模塊以及模塊的依賴模塊都調(diào)用 getModuleInfo
方法就行分析,最終返回一個包含所有模塊信息的對象
const parseModules = file => { // 定義依賴圖 const depsGraph = {} // 首先獲取入口的信息 const entry = getModuleInfo(file) const temp = [entry] for (let i = 0; i < temp.length; i++) { const item = temp[i] const deps = item.deps if (deps) { // 遍歷模塊的依賴,遞歸獲取模塊信息 for (const key in deps) { if (deps.hasOwnProperty(key)) { temp.push(getModuleInfo(deps[key])) } } } } temp.forEach(moduleInfo => { depsGraph[moduleInfo.file] = { deps: moduleInfo.deps, code: moduleInfo.code } }) console.log(depsGraph) return depsGraph } parseModules('./src/index.js')
獲得的depsGraph對象如下圖:
我們最終得到的模塊分析數(shù)據(jù)如上圖所示,接下來,我們就要根據(jù)這里獲得的模塊分析數(shù)據(jù),來生產(chǎn)最終瀏覽器運(yùn)行的代碼。
3. 生成最終代碼
在我們實(shí)現(xiàn)之前,觀察上一節(jié)最終得到的依賴圖,可以看到,最終的code里包含exports以及require這樣的語法,所以,我們在生成最終代碼時,要對exports和require做一定的實(shí)現(xiàn)和處理
我們首先調(diào)用之前說的parseModules方法,獲得整個應(yīng)用的依賴圖對象:
const bundle = file => { const depsGraph = JSON.stringify(parseModules(file)) }
接下來我們應(yīng)該把依賴圖對象中的內(nèi)容,轉(zhuǎn)換成能夠執(zhí)行的代碼,以字符串形式輸出。 我們把整個代碼放在自執(zhí)行函數(shù)中,參數(shù)是依賴圖對象
const bundle = file => { const depsGraph = JSON.stringify(parseModules(file)) return `(function(graph){ function require(file) { var exports = {}; return exports } require('${file}') })(${depsGraph})` }
接下來內(nèi)容其實(shí)很簡單,就是我們?nèi)〉萌肟谖募腸ode信息,去執(zhí)行它就好了,使用eval函數(shù)執(zhí)行,初步寫出代碼如下:
const bundle = file => { const depsGraph = JSON.stringify(parseModules(file)) return `(function(graph){ function require(file) { var exports = {}; (function(code){ eval(code) })(graph[file].code) return exports } require('${file}') })(${depsGraph})` }
上面的寫法是有問題的,我們需要對file做絕對路徑轉(zhuǎn)化,否則 graph[file].code
是獲取不到的,定義adsRequire方法做相對路徑轉(zhuǎn)化為絕對路徑
const bundle = file => { const depsGraph = JSON.stringify(parseModules(file)) return `(function(graph){ function require(file) { var exports = {}; function absRequire(relPath){ return require(graph[file].deps[relPath]) } (function(require, exports, code){ eval(code) })(absRequire, exports, graph[file].code) return exports } require('${file}') })(${depsGraph})` }
接下來,我們只需要執(zhí)行bundle方法,然后把生成的內(nèi)容寫入一個JavaScript文件即可
const content = bundle('./src/index.js') // 寫入到dist/bundle.js fs.mkdirSync('./dist') fs.writeFileSync('./dist/bundle.js', content)
最后,我們在index.html引入這個 ./dist/bundle.js
文件,我們可以看到控制臺正確輸出了我們想要的結(jié)果
4. bundle.js的完整代碼
const fs = require('fs') const path = require('path') const parser = require('@babel/parser') const traverse = require('@babel/traverse').default const babel = require('@babel/core') const getModuleInfo = file => { const body = fs.readFileSync(file, 'utf-8') console.log(body) const ast = parser.parse(body, { sourceType: 'module' }) // console.log(ast.program.body) const deps = {} traverse(ast, { ImportDeclaration({ node }) { const dirname = path.dirname(file); const absPath = './' + path.join(dirname, node.source.value) deps[node.source.value] = absPath } }) const { code } = babel.transformFromAst(ast, null, { presets: ["@babel/preset-env"] }) const moduleInfo = { file, deps, code } return moduleInfo } const parseModules = file => { // 定義依賴圖 const depsGraph = {} // 首先獲取入口的信息 const entry = getModuleInfo(file) const temp = [entry] for (let i = 0; i < temp.length; i++) { const item = temp[i] const deps = item.deps if (deps) { // 遍歷模塊的依賴,遞歸獲取模塊信息 for (const key in deps) { if (deps.hasOwnProperty(key)) { temp.push(getModuleInfo(deps[key])) } } } } temp.forEach(moduleInfo => { depsGraph[moduleInfo.file] = { deps: moduleInfo.deps, code: moduleInfo.code } }) // console.log(depsGraph) return depsGraph } // 生成最終可以在瀏覽器運(yùn)行的代碼 const bundle = file => { const depsGraph = JSON.stringify(parseModules(file)) return `(function(graph){ function require(file) { var exports = {}; function absRequire(relPath){ return require(graph[file].deps[relPath]) } (function(require, exports, code){ eval(code) })(absRequire, exports, graph[file].code) return exports } require('${file}') })(${depsGraph})` } const build = file => { const content = bundle(file) // 寫入到dist/bundle.js fs.mkdirSync('./dist') fs.writeFileSync('./dist/bundle.js', content) } build('./src/index.js')
2. 手寫 loader 和 plugin
2.1 如何自己實(shí)現(xiàn)一個 loader
loader本質(zhì)上就是一個函數(shù),這個函數(shù)會在我們在我們加載一些文件時執(zhí)行
2.1.1 如何實(shí)現(xiàn)一個同步 loader
首先我們初始化一個項(xiàng)目,項(xiàng)目結(jié)構(gòu)如圖所示:
其中index.js和webpack.config.js的文件內(nèi)容如下:
// index.js console.log('我要學(xué)好前端,因?yàn)閷W(xué)好前端可以: ') // webpack.config.js const path = require('path') module.exports = { mode: 'development', entry: { main: './src/index.js' }, output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js' } }
我們在根目錄下創(chuàng)建 syncLoader.js
,用來實(shí)現(xiàn)一個同步的loader,注意這個函數(shù)必須返回一個 buffer
或者 string
// syncloader.ja module.exports = function (source) { console.log('source>>>>', source) return source }
同時,我們在 webpack.config.js
中使用這個 loader
,我們這里使用 resolveLoader
配置項(xiàng),指定 loader
查找文件路徑,這樣我們使用 loader
時候可以直接指定 loader
的名字
const path = require('path') module.exports = { mode: 'development', entry: { main: './src/index.js' }, output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js' }, resolveLoader: { // loader路徑查找順序從左往右 modules: ['node_modules', './'] }, module: { rules: [ { test: /\.js$/, use: 'syncLoader' } ] } }
接下來我們運(yùn)行打包命令,可以看到命令行輸出了source內(nèi)容,也就是loader作用文件的內(nèi)容。
接著我們改造我們的loader:
module.exports = function (source) { source += '升值加薪' return source }
我們再次運(yùn)行打包命令,去觀察打包后的代碼:
這樣,我們就實(shí)現(xiàn)了一個簡單的loader,為我們的文件增加一條信息。 我們可以嘗試在 loader
的函數(shù)里打印 this
,發(fā)現(xiàn)輸出結(jié)果是非常長的一串內(nèi)容, this
上有很多我們可以在 loader
中使用的有用信息,所以,對于 loader
的編寫,一定不要使用箭頭函數(shù),那樣會改變 this
的指向。
一般來說,我們會去使用官方推薦的 loader-utils
包去完成更加復(fù)雜的 loader
的編寫
我們繼續(xù)安裝 loader-utils
,版本是 ^2.0.0
我們首先改造 webpack.config.js
:
const path = require('path') module.exports = { mode: 'development', entry: { main: './src/index.js' }, output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js' }, resolveLoader: { // loader路徑查找順序從左往右 modules: ['node_modules', './'] }, module: { rules: [ { test: /\.js$/, use: { loader: 'syncLoader', options: { message: '升值加薪' } } } ] } }
注意到,我們?yōu)槲覀兊?loader
增加了 options
配置項(xiàng),接下來在loader函數(shù)里使用loader-utils獲取配置項(xiàng)內(nèi)容,拼接內(nèi)容,我們依然可以得到與之前一樣的打包結(jié)果
// syncLoader.js const loaderUtils = require('loader-utils') module.exports = function (source) { const options = loaderUtils.getOptions(this) console.log(options) source += options.message // 可以傳遞更詳細(xì)的信息 this.callback(null, source) }
這樣,我們就完成了一個簡單的同步 loader
的編寫
2.1.2 如何實(shí)現(xiàn)一個異步 loader
和同步loader的編寫方式非常相似,我們在根目錄下建立一個asyncLoader.js的文件,內(nèi)容如下:
const loaderUtils = require('loader-utils') module.exports = function (source) { const options = loaderUtils.getOptions(this) const asyncfunc = this.async() setTimeout(() => { source += '走上人生顛覆' asyncfunc(null, res) }, 200) }
注意這里的 this.async()
,用官方的話來說就是 Tells the loader-runner that the loader intends to call back asynchronously. Returns this.callback.
也就是讓webpack知道這個loader是異步運(yùn)行,返回的是和同步使用時一致的 this.callback
接下來我們修改webpack.config.js
const path = require('path') module.exports = { mode: 'development', entry: { main: './src/index.js' }, output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js' }, resolveLoader: { // loader路徑查找順序從左往右 modules: ['node_modules', './'] }, module: { rules: [ { test: /\.js$/, use: [ { loader: 'syncLoader', options: { message: '走上人生巔峰' } }, { loader: 'asyncLoader' } ] } ] } }
注意loader執(zhí)行順序是從下網(wǎng)上的,所以首先為文本寫入‘升值加薪',然后寫入‘走上人生巔峰'
到此,我們簡單介紹了如何手寫一個 loader
,在實(shí)際項(xiàng)目中,可以考慮一部分公共的簡單邏輯,可以通過編寫一個 loader
來完成(比如國際化文本替換)
2.2 如何自己實(shí)現(xiàn)一個 plugin
plugin
通常是在 webpack
在打包的某個時間節(jié)點(diǎn)做一些操作,我們使用 plugin
的時候,一般都是 new Plugin()
這種形式使用,所以,首先應(yīng)該明確的是, plugin
應(yīng)該是一個類。
我們初始化一個與上一接實(shí)現(xiàn)loader時候一樣的項(xiàng)目,根目錄下創(chuàng)建一個 demo-webpack-plugin.js
的文件,我們首先在 webpack.config.js
中使用它
const path = require('path') const DemoWebpackPlugin = require('./plugins/demo-webpack-plugin') module.exports = { mode: 'development', entry: { main: './src/index.js' }, output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js' }, plugins: [ new DemoWebpackPlugin() ] }
再來看 demo-webpack-plugin.js
的實(shí)現(xiàn)
class DemoWebpackPlugin { constructor () { console.log('plugin init') } apply (compiler) { } } module.exports = DemoWebpackPlugin
我們在 DemoWebpackPlugin
的構(gòu)造函數(shù)打印一條信息,當(dāng)我們執(zhí)行打包命令時,這條信息就會輸出, plugin
類里面需要實(shí)現(xiàn)一個 apply
方法, webpack
打包時候,會調(diào)用 plugin
的 aplly
方法來執(zhí)行 plugin
的邏輯,這個方法接受一個 compiler
作為參數(shù),這個 compiler
是 webpack
實(shí)例
plugin的核心在于,apply方法執(zhí)行時,可以操作webpack本次打包的各個時間節(jié)點(diǎn)(hooks,也就是生命周期勾子),在不同的時間節(jié)點(diǎn)做一些操作
關(guān)于webpack編譯過程的各個生命周期勾子,可以參考 Compiler Hooks
同樣,這些hooks也有同步和異步之分,下面演示 compiler hooks
的寫法,一些重點(diǎn)內(nèi)容可以參考注釋:
class DemoWebpackPlugin { constructor () { console.log('plugin init') } // compiler是webpack實(shí)例 apply (compiler) { // 一個新的編譯(compilation)創(chuàng)建之后(同步) // compilation代表每一次執(zhí)行打包,獨(dú)立的編譯 compiler.hooks.compile.tap('DemoWebpackPlugin', compilation => { console.log(compilation) }) // 生成資源到 output 目錄之前(異步) compiler.hooks.emit.tapAsync('DemoWebpackPlugin', (compilation, fn) => { console.log(compilation) compilation.assets['index.md'] = { // 文件內(nèi)容 source: function () { return 'this is a demo for plugin' }, // 文件尺寸 size: function () { return 25 } } fn() }) } } module.exports = DemoWebpackPlugin
我們的這個 plugin
的作用就是,打包時候自動生成一個 md
文檔,文檔內(nèi)容是很簡單的一句話
上述異步hooks的寫法也可以是以下兩種:
// 第二種寫法(promise) compiler.hooks.emit.tapPromise('DemoWebpackPlugin', (compilation) => { return new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 1000) }).then(() => { console.log(compilation.assets) compilation.assets['index.md'] = { // 文件內(nèi)容 source: function () { return 'this is a demo for plugin' }, // 文件尺寸 size: function () { return 25 } } }) }) // 第三種寫法(async await) compiler.hooks.emit.tapPromise('DemoWebpackPlugin', async (compilation) => { await new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 1000) }) console.log(compilation.assets) compilation.assets['index.md'] = { // 文件內(nèi)容 source: function () { return 'this is a demo for plugin' }, // 文件尺寸 size: function () { return 25 } } })
最終的輸出結(jié)果都是一樣的,在每次打包時候生成一個md文檔
到此為止,本文介紹了webpack打包的基本原理,以及自己實(shí)現(xiàn)loader和plugin的方法。希望本文內(nèi)容能對大家對webpack的學(xué)習(xí),使用帶來幫助。更多相關(guān)webpack打包loader和plugin內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- vue-cli5.0?webpack?采用?copy-webpack-plugin?打包復(fù)制文件的方法
- vue 解決uglifyjs-webpack-plugin打包出現(xiàn)報錯的問題
- html-webpack-plugin修改頁面的title的方法
- webpack DllPlugin xxx is not defined解決辦法
- 用npm安裝vue和vue-cli,并使用webpack創(chuàng)建項(xiàng)目的方法
- vue項(xiàng)目webpack中Npm傳遞參數(shù)配置不同域名接口
- 詳解node.js中的npm和webpack配置方法
- 如何基于webpack創(chuàng)建plugin并發(fā)布npm包
相關(guān)文章
bootstrap中使用google prettify讓代碼高亮的方法
使用google prettify 讓代碼高亮非常漂亮,接下來通過本文給大家介紹bootstrap中使用google prettify讓代碼高亮的方法,感興趣的朋友一起看看吧2016-10-10數(shù)據(jù)結(jié)構(gòu)中的各種排序方法小結(jié)(JS實(shí)現(xiàn))
下面小編就為大家?guī)硪黄獢?shù)據(jù)結(jié)構(gòu)中的各種排序方法小結(jié)(JS實(shí)現(xiàn))。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-07-07Javascript document.referrer判斷訪客來源網(wǎng)址
用簡單幾行的javascript,就可抓到使用的來源,以及作出一些防范的措施。2009-12-12JS實(shí)現(xiàn)兼容各種瀏覽器的高級拖動方法完整實(shí)例【測試可用】
這篇文章主要介紹了JS實(shí)現(xiàn)兼容各種瀏覽器的高級拖動方法,以完整實(shí)例形式分析了JS實(shí)現(xiàn)響應(yīng)鼠標(biāo)事件動態(tài)修改頁面元素的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2016-06-06