显微镜下的webpack4入门

前端的构建打包工具不少,好比grunt,gulp。相信这二者你们应该是耳熟能详的,上手相对简单,并且所需手敲的代码都是比较简单的。而后webpack的出现,让这二者打包工具都有点失宠了。webpack比起前二者打包工具,对于前端程序员JS编程能力的要求仍是挺高的。不过须要兼容ie8及如下的小伙伴们,就不要考虑webpack了,他很傲娇地不兼容!css

webpack 前期准备

webpack,这是一个组合词“web”+“pack”,web就是网站的意思,“pack”有打包的意思,webpack组合在一块儿就是网站打包的意思,这个名字至关暴力简单明了啊。webpack这款工具虽然很难学,可是自由度很大,玩转以后有种为所欲为的感受。html

在学习webpack以前,有几个基础的概念:前端

  • JavaScript,若是这个编程能力不过关,好比不清楚ES6的语法,那么webpack学起来有些费力,仍是要先去学习基础知识。
  • nodejs,关于nodejs的平常用法,仍是须要了解的,否则webpack改如何启动,都无从下手。
  • CommonJS,这个规范是须要学习下的,webpack的配置文件就是按照这个规则。
  • 若是以上几个技能都具有,那么恭喜咱们能够开始webpack的学(求)习(虐)之旅了。

webpack 打包原理

在使用webpack以前,咱们须要了解webpack的工做原理。webpack打包出来的JS不只仅是压缩混淆咱们的源文件,并且还对它作了其余的处理。node

下面是webpack打包出来的JS文件和源文件:webpack

  • "./src/index.js"源文件
let str="index"
console.log(str)
  • webpack打包后
(function(modules) { // webpackBootstrap
    /*此处省略N+1行*/
    return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
({
    "./src/index.js":(function(module, exports) {
        eval("let str=\"index\"\r\nconsole.log(str)\n\n//# sourceURL=webpack:///./src/index.js?");
    })
});

是否是感受原本小巧的JS,一会儿变得臃肿了??彷佛用webpack没有意义啊!不只不能忙我压缩文件,还把源文件变胖了。git

不要急,咱们再看一个例子:程序员

  • "./src/index.js"源文件
require("./page1.js")
let str="index"
console.log(str)
  • "./src/page1.js"源文件
let str="page1"
console.log(str)
  • webpack打包后
(function(modules) { // webpackBootstrap
    /*此处省略N+1行*/
    return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
({
    "./src/index.js": (function(module, exports, __webpack_require__) {
        eval("__webpack_require__(/*! ./page1.js */ \"./src/page1.js\")\r\nlet str=\"index\"\r\nconsole.log(str)\n\n//# sourceURL=webpack:///./src/index.js?");
    }),
    "./src/page1.js":(function(module, exports) {
        eval("let str=\"page1\"\r\nconsole.log(str)\n\n//# sourceURL=webpack:///./src/page1.js?");
    })
});

当有模块导入的时候,这个胖JS就展示了他真正的实力。经过__webpack_require__来实现JS之间导入的功能。至关于咱们再也不须要用requirejs,seajs此类包管理器管理咱们的前端模块了。webpack帮助咱们完成了此类工做。是否是忽然以为这个胖JS不胖了。github

webpack的打包原理,就是将各个模块变成字符串,存入健值或者数组之中,而后每一个模块之间的关系,经过__webpack_require__这个方法来实现。最后经过eval这个函数将字符串变成可执行代码。web

若是你们对__webpack_require__的实现原理感兴趣,能够本身打包一个文件,不要压缩混淆,而后研究研究。typescript

对webpack的期许

webpack这个工具,不可能只有打包压缩这个功能吧。既然是前端工具,那么必然要具有如下功能:

  • 代码处理,如打包,编译等
  • 自动生成HTML文件,好比模板生成页面
  • 本地服务器,这个是必备功能,否则没法调试页面
  • 自动编译代码,刷新浏览器,这个你们喜欢称之为hot replacement(热替换,热更新),也就是(修改过的)部分更新
  • 那咱们逐步来了解下webpack这些功能该如何实现。

webpack从0开始

若是你以前并未使用过webpack,那么就须要安装一下webpack,顺便学习下如何启动webpack。

STEP 1 INSTALL

webpack从4开始,webpack分红了两个包一个webpack一个webpack-cli,因此安装的时候要安装两个包,以及这个包咱们是工具,非网站所依赖的包,因此记得放在开发依赖包之中。

npm install webpack webpack-cli -save-dev

也许咱们想能够直接安装webpack,不要webpack-cli。可是现实很残酷,若是没有安装CLI,系统就会告诉你,cli是必不可少的,否则webpack就罢工了。

One CLI for webpack must be installed.

STEP2 RUN

安装好了以后,咱们应该怎么运行呢?这里有两个途径:

  • npm v8.5以上有一个操做叫作npx,这个是干吗的呢,是帮忙咱们直接执行.bin,目录下的文件。node_modules\.bin\webpack.cmd在这个路径下有webpack的执行命令,咱们能够打开看看。当咱们npx webpack的时候,就是运行了这个文件。
  • 经过配置package.json来运行文件,有个字段叫作scripts,咱们加一个start,而后后面跟上命令。到时候咱们呼唤npm start就要能够运行webpack了。
"scripts": {
    "start": "webpack --config webpack.config.js"
 }

webpack4开始支持零配置,也就是说我不用写webpack.config.js也能够运行。那咱们就运行试试,结果出现了一个警告:

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

这个警告就是告诉咱们,webpack4中的mode参数默认是production,因此若是是development的状况就必定要配置了。感受是零配置彷佛是很是牛逼的一个操做,可是实际上仍是须要手动配置的,由于这个零配置只是帮咱们作掉了一些简单的事,好比线上就压缩JS,开发版就不压缩JS,还有一些默认的路径之类的。实际上开发的时候,默认的路径确定是不够用的。咱们仍是老老实实写配置吧。

咱们配置一下,而且运行一下,在开发环境下打包,生成了一个/dist/main.js文件。奇怪个人html文件怎么没有打包过来?对,HTML文件须要咱们本身在dist之中建立的,也就是/dist/index.html。而且路径要写好即将生成的JS连接。好比/dist/main.js在html中引入,我就须要写成<script src="./main.js"></script>

module.exports = {
    mode:"development",
};

这个配置文件,你们都没有以为写法很熟悉?对!就是CommonJs规范!下一节会详细解释webpack.config.js该如何配置。

webpack的心脏——webpack.config.js

webpack的一切操做都配置在webpack.config.js之中,能够说配好webpack.config.js,咱们就能够坐等新鲜出炉的网站了。

从官方文档来看,webpack一共有5个主要地配置参数

  • Entry:切入点,也就是JS程序入口,按照这个入口开始建立模块依赖,默认./src/index.js
  • Output:输出口,打包程序的安放位置,默认./dist/main.js
  • Loaders:加载器,将除了JS和JSON之外的文件解析加载,好比txt,css等等。
  • Plugins:插件,能够作一些更加牛逼的效果,通常要new一个插件对象。
  • Mode(新增):productiondevelopment,这个是webpack4新增的一个属性,用于区分开发版与线上版,也是很贴心的设置了。

Entry&Output,以及chunk的概念

在学些webpack的配置以前,咱们最早接触的就是输入Entry和输出Output的配置。这里须要引入一个chunk的概念,咱们在配置Entry的时候,有时候会设置好多个入口,这每个入口都是一个chunk,也就是说chunk是根据Entry的配置而来的。你们必定要区分清楚chunk和module的概念啊。module就是编程的时候,咱们所写的一块一块的功能块,而后模块之间会有依赖。而后chunk只是将当前模块和他的依赖模块,一块儿打包起来的代码块。

配置Entry,切入点JS入口也不是件容易的事。

Entry配置

  • 单一入口,单个文件。整个程序只有一个JS,这个配置就很简单了,我么也能够不配置,由于默认./src/index.js。单个文件之间传入字符串便可。
entry: '须要打包的JS的相对或者绝对地址'
  • 单一入口,多个文件。有时候咱们有好多独立的JS文件,可是我只想导出一个JS,这个时候就须要传入数组了。
entry: ["待打包JS-1","待打包JS-2"]
  • 多个入口,单个文件。这个时候咱们就要配置健值了,都是默认值,怎么识别谁是谁。通常来讲一个HTML只须要一个chunk,也就是一个入口点。因此这个通常用于多张页面的配置。
entry: {
    JS1: "待打包JS-1",
    JS2: "待打包JS-2"
}
  • 多个入口,多个文件。前面提到一个HTML只须要一个入口点,因此这里咱们能够借鉴数组来完成此操做。
entry: {
    JS1: ["待打包JS1-1","待打包JS1-2"],
    JS2: ["待打包JS2-1","待打包JS2-2"]
}

Output配置

输出口,安放打包好的JS,不配置就打包到默认文件,默认./dist/main.js

若是不须要分入口点,整个网站用一个JS。那么配置一个文件名就能够了。

output: {
    filename: 'bundle.js',
}

须要指定文件夹的操做,就再加一个path字段便可。

output: {
    filename: 'bundle.js',
    path: __dirname + '/dist'
}

然而现实中,咱们不可能只有一个JS,因此这个时候咱们就须要配置多个输出口,不过这个不像entry能够配置健值。可是有一个很简便的办法filename: '[name].js',文件名咱们用[name],这样打包出来的Js文件就会按照Entry配置的chunk名,来命名了。

固然咱们常常回碰到CDN的问题,一个JS会被缓住,这时候咱们能够用[hash]这个参数,来帮咱们filename: '[name].[hash].js'这样每次生成的JS名就不同了。

LOADER,模块的概念

在webpack中,任何文件均可以变成一个模块,而后被打包到JS之中。可是JS只认识JS,像CSS,或者typescript这类的非标准JS,该如何处理?这个时候Loader就出现了,他帮助webpack将CSS此类文件变成JS可识别的内容而后打包。全部的loader都须要额外下载安装,这里以最经常使用的CSS为例子,看咱们如何将CSS打包到JS之中。

  • 安装css-loader这个加载器
npm install --save-dev css-loader

关于css-loader的用法,你们能够参考下官网

  • 在webpack中配置。你们不要把loader的配置名写成了loader,他的在webpack中的配置名是module.rule
module: {
    rules: [
        {
          test: /\.css$/,
          use: [
            { loader: 'style-loader'},
            { loader: 'css-loader',options: {modules: true}}
          ]
        }
      ]
}
  • 添加style-loader

也就是说loader全部的配置都在rules之下。这里我还配置了style-loader,那么咱们既然又了css-loader为何还要style-loader呢?感受很累赘啊。那么接下来就要说说这两个loader的不一样了。

打开styleloader的官网,咱们能够发现:

Adds CSS to the DOM by injecting a <style> tag

也就是说style-loader就干一件事就是将咱们处理好的CSS插入到DOM之中,不然咱们的CSS只编译不生效。

若是咱们不喜欢内联样式,而且以为CSS文件不必编译到JS文件之中,那么咱们能够直接引入一个文件。咱们能够这样配置。

module: {
    rules: [
        {
          test: /\.css$/,
          use: [
            { loader: 'style-loader/url'},
            { loader: "file-loader" }
          ]
        }
      ]
}

利用style-loader/urlfile-loader来加载文件。这个时候会在咱们的生产文件夹下新建一个css文件,而后js中会加载这个新建的css文件的路径。咱们无需在页面上配置link,js会帮助咱们自动生成一个link,引入咱们的css文件。这样咱们就不用将css和js打包到一块儿啦。

PLUGINS,更多优化操做

若是说loader只是对于JS的一个操做,好比将CSS转化到JS之中啦,那么plugins的功能就更加普遍,并不局限加载编译JS,好比HTML文件的操做。

这里有一个我刚开始的遇到的问题,就是:

webpack主要是负责JS的编译管理,那么个人HTML文件呢?难道要我一个个在dist之中建立好吗??

这个时候HTML Webpack Plugin出现啦,这个插件是专门用于建立管理HTML的。

首先是安装npm i --save-dev html-webpack-plugin,而后是配置webpack:

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    mode:"development",
    plugins: [
        new HtmlWebpackPlugin()
    ]
}

通常插件都是建立一个新的实例,而后加入plugins这个数组之中。

而后咱们来看看这个HtmlWebpackPlugin插件,这个插件很强大,咱们不只能够控制模版,还能够配置页面内容,像下方这样。

test.html

<body>
    <%= htmlWebpackPlugin.options.title %>
</body>

webpack.config.js

plugins: [
    new HtmlWebpackPlugin(), //生成自动的index.html
    new HtmlWebpackPlugin({  // 生成一个test.html
        title: 'Custom template using Handlebars',
        filename: 'test.html',
        template: path.join(__dirname,'src/test.html')
    })
],

由上述例子能够看出,为了保证插件的灵活性,好比我每一个页面的配置不同,咱们就能够new好几个插件来处理咱们的html文件。一个实例处理一个页面。

webpack4.0的新特点——mode

MODE有三个参数productiondevelopmentnone,前两个是有预设的插件,而最后一个则是什么都没有,也就是说设置为none的话,webpack就是最初的样子,无任何预设,须要从无到有开始配置。

咱们来研究下他们之间的配置的区别,首先是二者都有的一个new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development|production") }),这个是用来让咱们能够直接在js中引用"process.env.NODE_ENV"的值,这样就能够在JS之中经过这个值来区别开发板与先上版本的不一样脚本。

编译以前的index.js

console.log(process.env.NODE_ENV)

编译以后的index.js

console.log("development")

咱们能够看到直接将咱们的process.env.NODE_ENV替换成了因此定义的内容。这个小功能能够帮助咱们在写业务JS的时候,区分线上版本与开发版本。

development

咱们接着看看其余的开发中使用的插件NamedModulesPluginNamedChunksPlugin,本来咱们的webpack并不会给打包的模块加上姓名,通常都是按照序号来,从0开始,而后加载第几个模块。这个对机器来讲无所谓,查找载入很快,可是对于人脑来讲就是灾难了,因此这个时候给各个module和chunk加上姓名,便于开发的时候查找。

在没有mode的状况下,这些插件须要本身配置,而有了mode以后,咱们的配置就能够省略了。

// webpack.development.config.js
module.exports = {
+ mode: 'development'
- devtool: 'eval',
- plugins: [
-   new webpack.NamedModulesPlugin(),
-   new webpack.NamedChunksPlugin(),
-   new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
- ]
}

production

在线上版本中,咱们第一个须要处理的就要混淆&压缩JS了吧。在线上mode中,自带JS混淆压缩,能够说这个功能很方便了。

// webpack.production.config.js
module.exports = {
+  mode: 'production',
-  plugins: [
-    new UglifyJsPlugin(/* ... */),
-    new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
-    new webpack.optimize.ModuleConcatenationPlugin(),
-    new webpack.NoEmitOnErrorsPlugin()
-  ]
}

本地服务器&&hot refresh

官方文档一共给出了3中实时编译的方法:--watch,webpack-dev-server``和webpack-dev-middleware`

--watch webpack-dev-server webpack-dev-middleware
实时编译 yes yes yes
服务器 no yes yes
hot no yes yes
代码上手 简单 中等 稍困难

--watch

--watch是个好方法,运行以后,会自动给咱们编译文件。可是浏览器须要手动刷新才能出现最新的内容。

webpack-dev-server

webpack-dev-server虽然,能够直接在config中配置参数,可是仍是须要安装一下,才可使用。

npm install --save-dev webpack-dev-server

webpack不产出任何编译后的文件。他只提供内存中的代码,伪装是真是的代码。若是你但愿在其余目录中读取文件。能够更换publicPath 选项。每次修改都会实时编译。

可是使用webpack-dev-server,修改文件,并不会实时刷新浏览器,咱们须要一些配置才能够。

首先须要在pligns中加入new webpack.HotModuleReplacementPlugin()

plugins: [
        new webpack.HotModuleReplacementPlugin()
    ],
    devServer: {
        contentBase: path.join(__dirname, 'dist'),
        publicPath:"/",
    },

webpack-dev-server虽然很方便,配置也简单,可是他编译出来的文件与npx webpack编译出来的并不同,所以调试起来未必很方便。

webpack-dev-middleware

看见middleware就应该知道这个是一个中间件,用于连接webpack的编译功能和其余nodejs服务器框架的桥梁,这边咱们选择express这个框架。

首先是安装这两个包。

npm install --save-dev express webpack-dev-middleware

这个比webpack-dev-server要复杂一些,还须要安装一个express。可是这个的编译的内容是会写入dist文件的,实时更新,彻底按照webpack的编译来。她的原理就是先执行webpack,在更新到服务器上,这样咱们访问的就是最新的内容了。

既然是中间件,那么就不是webpack亲生的,就须要在webpack-dev-server配置的基础上加点料。

咱们要在须要监控的入口点加入监控的js,像这样写:

entry:{
    index:['webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000&reload=true',path.join(__dirname,"/src/index.js")],
},

接着就是server.js的编写,想要写好这一部分,你们要先学会express,以及express中间件的用法。而后再是将webpack挂载到express之上。

const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');

const app = express();
const config = require('./webpack.config.js');
const compiler = webpack(config);

app.use(webpackDevMiddleware(compiler, {
  publicPath: config.output.publicPath
}));

app.use(require("webpack-hot-middleware")(compiler));

app.listen(8080, function () {
  console.log('Example app listening on port 8080!\n');
});

这样配置虽然麻烦,可是咱们能看到实时编译的JS文件,对于网站的总体细节把控会更好。

总结

感受写了一篇超长的入门文章,列出了webpack的配置用法,以及webpack插件的用法,能够说webpack插件是webpack之魂,扩展了许多其余的功能。还有如何实时编译咱们的网站。

相关文章
相关标签/搜索