原文链接:
https://medium.com/@rajaraodv/webpack-the-confusing-parts-58712f8fcad9
webpack的核心哲学
1. 任何皆模块
正如js文件可以是"modules",任何其他的文件,比如css, images, html都可以被视为modules。也就是说,你可以通过require("myJSfile.js")来加载js文件,也可以通过require("mycssFile.css")加载css文件。这也就意味着我们可以将任何工作成果视为更小的可管理的构建,可用于重用。
2. 仅仅在你需要使用的时候,仅仅加载你需要的asset. 典型的,打包器接收所有的模块输入而最终生成以恶搞巨大的单个"bundle.js"文件.但是很多真实的应用,这个bundel.js文件可能会达到10MB-15MB的大小!因此,这种情况下会导致加载非常慢。webpack为了处理这种bundle过大的问题,webpack提供了几个好用的功能用于split你的代码,并且产生出多个"bundle" files,并且可以async异步地加载parts of the app,以便仅仅加载你当前需要的部分。
下面我们来一个个探讨容易搞不清楚的webpack topic
Development vs production
首先我们要铭记在心的是webpack有非常朵的好功能,而这些功能中很大一部分仅仅是为"development-only"的功能,而一部分是"production-only"的,而剩下的部分是既可用于开发环境,又可用于生产环境。
典型地,大多数项目通过使用两个大的webpack config file来分别处理dev和prod两种场景。
要创建bundle,你可能会在package.json中这样写:
"scripts":{ // npm run build to build production bundles "build": "webpack --config webpack.config.prod.js", // npm run dev to generate development bundles and run dev server "dev": "webpack-dev-server" }
webpack CLI vs webpack-dev-server
webpack,作为打包工具,提供以下两个接口,理解这一点非常重要:
1. webpack cli tool---这是默认的接口(随着webpack的安装而存在于.bin目录中)
2. webpack-dev-server tool ---一个node.js server(你需要单独安装它)
webpack CLI(非常适合做prodcution build)
这个命令行工具通过cli接收一些配置option,或者通过一个config file来接收这些option(默认为webpack.config.js配置文件),这些配置选项将用于webpack的打包过程。
虽然你可能是通过使用cli来开始学习webpack的,但是实际上命令行工具往往更多用于创建生产环境下使用的bundle.
用法:
OPTION 1: //Install it globally npm install webpack --g //Use it at the terminal $ webpack //<--Generates bundle using webpack.config.js OPTION 2 : //Install it locally & add it to package.json npm install webpack --save //Add it to package.json's script “scripts”: { “build”: “webpack --config webpack.config.prod.js -p”, ... } //Use it by running the following: "npm run build"
webpack-dev-server(非常适合创建开发的build,并serve静态的assets)
webpack-dev-server是一个运行于8080端口的expresss nodejs server.这个server会自己调用webpack本身实现构建,并且server构建出来的bundle.这个server的好处是它可以提供比如"Live Reloading"或者"Hot Module Replacement(HMR)"的实用功能。
npm install webpack-dev-server --save //Use it at the terminal $ webpack-dev-server --inline --hot OPTION 2: // Add it to package.json's script “scripts”: { “start”: “webpack-dev-server --inline --hot”, ... } // Use it by running $ npm start Open browser at: http://localhost:8080
webpack vs webpack-dev-server options
需要说明的是,比如"inline"或者"hot"这些配置选项仅仅是对webpack-dev-server来适用的。而比如"hide-modules"仅仅针对webpack cli来说是适用的。
webpack-dev-server CLI options vs config options
另外需要说明的一点是:你可以通过以下两种方式来传入webpack-dev-server配置选项options:
1. 通过webpack.config.js的"devServer对象
2.通过webpack-dev-server的CLI options传入:
//Via CLI webpack-dev-server --hot --inline //Via webpack.config.js devServer: { inline: true, hot:true }
我在测试中发现,有时通过devServer配置选项传入参数(hot:true, inline:true)并不总是能够工作,所以,我更喜欢通过在package.json中的cli option来传入对应的参数:
//package.json { scripts: {“start”: “webpack-dev-server --hot --inline”} }
"hot" vs "inline" webpack-dev-server options:
"inline"选项使得针对整个页面实现"live reloading"成为可能。"hot"选项使能了"hot module reloading"功能,而这个功能可以仅仅reload那些被变更过的component(而不是整个页面重新加载).如果我们同时传入两个配置选项,那么当source变化时,webpack-dev-server将会首先试图做HRM,如果HMR不work,则会reload整个页面。
//When the source changes, all 3 options generates new bundle but, //1. doesn't reload the browser page $ webpack-dev-server //2. reloads the entire browser page $ webpack-dev-server --inline //3. reloads just the module(HMR), or the entire page if HMR fails $ webpack-dev-server --inline --hot
"entry" --String Vs Array Vs Object
Entry这个配置项告诉webpack应用的root module或者starting point在哪里。这个配置项可以是string,可以是array,也可以是object.这个灵活性可能让我们搞得糊涂,但是这些不同类型的数据实际上是用于不同的目的的。
如果你有一个single starting point的话,那么string, array, object都是一样的结果。
entry-array
但是,如果你想追加多个互不依赖的文件,那么可以使用array格式
比如,你可能需要"googleAnalytics.js"到你的html中,那么你可以告诉webpack追加该analytics.js到bundle.js的后面:
entry-object
如果你有一个多页的web应用,有多个html文件(index.html, profile.html等),而不是一个SPA w/ multi-views,
那么你可以通过entry object告诉webpack需要一次性创建多个bundles。
下面的配置将产生两个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" vs "publicPath"
output告诉webpack我们将最终的输出存放在哪里一级如何存放。这里两个参数:path和publicPath可能会产生歧义和误解。
"path"告诉webpack我们将最终的bundle存放在哪里。耳"publicPath"则被多个webpack plugin来使用用于当产生production build时更新在html,css文件中的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来import这些文件到js中。
例如:你可以使用bable-loader来转换es6写的js代码到浏览器可以认识的es5 javascript:
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(从右往左)
多个loaders可以级联起来,针对同一个文件做不同的转换。级联是从右往左工作的,同时使用" ! "来隔离
例如,我们如果有一个"mycssfile.css"文件,我们需要将它的内容dump到html的<style>css content</style>中。我们可以通过以下两个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,并且增加<style>css contents</style>并将这个字符串插入到index.html文件中
loaders Themselves can be configured
loaders可以通过传入不同的parameters以不同的方式来工作。
在下面的例子中,我们配置url-loader当image小于1024字节的话直接使用DataURLs。我们可以传入limit参数来指定这个大小。
.babelrc文件
babel-laoder使用"presets"配置选项使能如何转换es6为es5的过程,一级如何解析react’s 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”] }
Plugins
plugins是一些针对输出的bundle执行特别的工作的node modules.
例如,uglifyJSPlugin的作用是接收bundle.js作为输入并且混淆最小化该文件以减少文件的体积。
类似地,extract-text-webpack-plugin这个plugin则内部通过使用css-loader,style-loader来收集所有css内容并整合到一处并最终抽出这些css内容到一个单一的style.css文件中,并且在index.html中产生一个指向该style.css的link。
//webpack.config.js //Take all the .css files, combine their contents and it extract them to a single "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 ] }
需要说明的是,如果你想在html中inline使用这些css样式,你可以不使用该plugin,而仅仅通过使用css, style loaders达到目的:
module: { loaders: [{ test: /.css$/, loader: ‘style!css’ <--(short for style-loader!css-loader) }]
loaders vs plugins
loaders仅在bundle生成过程中或者生成之前针对单个的文件做转换。
而plugin则在bundle创建的结束之后针对整个bundle或者chunk level执行操作。一些像commonsChunksPlugin的插件则更进一步会修正bundles本身是如何创建的过程。
resolving file extensions
很多webpack config文件有一个resolve extensions属性,并有一个空字符串作为值。
这个空字符串用于帮助resolve imports without extensions,比如require("./myJSFile")或者import myJSFile从"./myJSFile”而不用加上文件的扩展名.
{
resolve: {
extensions: [‘’, ‘.js’, ‘.jsx’]
}
}