说到学习webpack的艰辛历程,那可真是一把鼻涕一把泪啊。我刚学习前端的时候,jquery的时代刚刚过去,入门前端就是学点js基础,会写一点css,而后直接用react和vue撸项目。你可能不知道为何要这么写,但照着别人的代码这么写是对的,就似懂非懂地开发项目了。css
至于webpack,那是什么鬼,我直接用vue-cli3或者create-react-app就行了,只要项目跑得起来,谁管你要用webpack配置什么东西。但理想是丰满的,现实是骨感的,咱们不可能只用别人搭好的脚手架写项目,要想实现不一样应用场景下一些业务上的功能开发,还得本身动手配,真香。谁让我走上了前端这条路呢,本身选的路,跪着也要继续啊。html
这篇文章是我学习webpack的笔记,看心情更新,有生之年应该能更完。前端
咱们编写的css和js代码运行在浏览器,能够直接在控制台的sources查看,若是不对代码进行压缩打包,代码的逻辑就很容易被别人看到,这对咱们项目是很是不利的。vue
找在线的打包代码网站上传代码,对代码进行压缩打包以后,再下载下来,放进咱们的项目里面,可谓是异常艰辛。java
YUI tool是07年左右出现的一个代码打包工具,和java的ant配合使用,打包css和js代码。由于当时的先后端还没分离,是后端主导前端,因此前端的代码都是嵌入到后端的项目里去的。node
随着业界requireJS模块化的概念不断催生,各类模块化的代码编写也是愈来愈复杂,这时grunt诞生了,直到今天,grunt的社区生态依旧没有衰减。他的本质就是运行在node上的命令行工具,它是一个任务运行器,将打包构建过程分红一个一个的任务进行, 好比:解析HTML,解析CSS,解析JS,压缩图片,压缩代码等。每完成一个任务,就将这个任务的完成结果存放到本地磁盘的一个temp目录中去,因此它的缺点也显而易见,就是打包速度很慢,由于打包的时候进行了本地磁盘的IO操做。react
为了解决grunt打包速度较慢的问题,gulp诞生了。gulp构建打包的本质和grunt同样,也是分为一个一个的任务去执行。但gulp有一个文件流的概念,它再也不把每一步构建的结果存放到磁盘中去,而是存放在内存中,这样的话在构建的下一个步骤能够直接在内存中读取上一步构建的结果,大大加快了构建的速度。jquery
官方文档介绍,本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序须要的每一个模块,而后将全部这些模块打包成一个或多个 bundle。webpack
为何如今使用webpack来打包项目的较多,有如下三个缘由。git
在webpack.config.js里进行webpack的基本配置
module.exports = {
entry:'./src/index.js',
output:'./dist/main,js',
module:{
rules:[
{ test: /\.txt$/, use: 'raw-loader' }
]
},
plugins:[
new HtmlWebpackPlugin({template: './src/index.html'})
]
}
复制代码
入口(entry):指示 webpack应该使用哪一个模块,来做为构建其内部依赖图的开始。webpack 会从入口找出有哪些模块和库是入口起点(直接和间接)依赖的。
出口(output):将打包成功的bundles文件输出到出口目录里。
loader:webpack自身只能识别JavaScript,没法识别非JavaScript文件。loader能够将全部类型的文件转换为webpack可以识别的有效模块。loader配置在module里的rules里,rules是一个数组,里面能够配置多个loader。
插件:loader 被用于转换某些类型的模块,而插件则能够用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到从新定义环境中的变量。插件接口功能极其强大,能够用来处理各类各样的任务。插件配置到plugins这个数组里,能够配置多个插件。
跟着个人命令,来实现咱们的第一个webpack程序
首先,确保电脑安装了nodeJS和npm,由于引入webpack须要依赖npm。
随便进入一个文件夹,打开终端,执行如下命令:
mkdir webpack
cd webpack
npm init -y
复制代码
npm init -y就是初始化咱们的项目,生成一个package.json文件,-y命令能够省去敲回车确认的步骤,直接生成默认的package.json文件。以后执行下面的命令,将咱们的webpack依赖安装进项目里。
npm install webpack webpack-cli --save-dev
复制代码
因为webpack和webpack-cli是配套使用的,因此都须要安装。--save-dev指的是将依赖安装进devDependencies而不是Dependencies里。devDenpendencies是开发环境,而Dependencies是生产环境。
安装好的webpack会放在项目node_modules里的bin目录里,能够运行下面的命令来检测webpack是否成功安装。
./node_modules/.bin/webpack -v
复制代码
在咱们刚才新建的webpack文件夹里建立一个叫webpack.config.js的配置文件,在webpack.config.js里作以下配置:
const path = require('path'); // 引入path模块
module.exports = {
entry:'./src/index.js', // 配置入口
output:{ // 配置出口
path:path.join(__dirname,'dist'), // path.join方法,用来拼接路径,__dirname是当前路径,这里拼接起来就是当前路径后面加上dist
filename:'bundle.js' // 打包的文件名
},
mode:'production' //设置为开发环境,不设置这个选项会报warning
}
复制代码
而后新建src目录,在src目录里新建两个文件,一个是index.js,也就是咱们的入口文件,另外一个是helloWebpack.js文件,在两个文件分别写如下代码:
helloWebpack.js
export function helloWebpack(){
return 'hello webpack';
}
复制代码
index.js
import { helloWebpack } from './helloWebpack';
document.write(helloWebpack());
复制代码
这里咱们在helloWebpack.js里写了一个函数,返回一个字符串,在index.js里引用这个函数,并打印在document上。
好了,代码编写完毕,你能够点击这里查看项目如今的目录结构
如今执行打包命令,因为webpack是在node_modules下的bin目录里,因此得执行如下命令才能生效,以下图所示:
./node_modules/.bin/webpack
复制代码
不过点开这个bundle.js发现里面的代码都写得很是奇怪还所有挤在一块儿,这就是js代码打包后的样子,这样咱们代码的逻辑就不会暴露出来了。
咱们在dist里新建一个index.html文件,在这个文件里引入bundle.js,验证bundles.js里的代码是否有效。
打开浏览器预览index.html,发现成功输出hello webpack。
在上面的例子中,咱们打包的时候须要执行 ./node_modules/.bin/webpack命令,很麻烦,有没有更简单的办法呢?实际上是有的,咱们能够在package.json里的scripts里添加一个build命令来构建项目。
package.json
{
"name": "demo1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.39.3",
"webpack-cli": "^3.3.7"
}
}
复制代码
这是添加完命令后的package.json的样子。接下来只须要在终端打开package.json所在的目录,执行命令
npm run build
复制代码
就能够顺利地将咱们的项目构建打包了,打包结果和上面的例子如出一辙(注意打包以前执行 rm -rf dist 命令将以前打包的结果删除。)
像上例这样写在scripts里的命令就是npm脚本。
"scripts": {
"build": "webpack"
},
复制代码
npm脚本scripts是一个对象,它的每个属性,对应一个脚本。咱们能够在scripts写入多条脚本,好比咱们常见的vue-cli3中package.json中的scripts长这个样子:
"scripts": {
"dev": "node build/dev-server.js",
"start": "node build/dev-server.js",
"build": "node build/build.js"
},
复制代码
在这里面,dev命令对应的脚本就是node build/dev-server.js,咱们执行npm run dev这个命令,等同于执行node build/dev-server.js。npm run start和npm run build同理。
在上例中,执行npm run build,等同于执行 ./node_modules/.bin/webpack。 为何会这么神奇呢?
由于每当执行npm run,就会自动新建一个 Shell,在这个 Shell 里面执行指定的脚本命令。所以,只要是 Shell(通常是 Bash)能够运行的命令,就能够写在 npm 脚本里面。
比较特别的是,npm run新建的这个 Shell,会将当前目录的node_modules/.bin子目录加入PATH变量,执行结束后,再将PATH变量恢复原样。
这意味着,当前目录的node_modules/.bin子目录里面的全部脚本,均可以直接用脚本名调用,而没必要加上路径。好比,当前项目里的依赖有webpack,直接写webpack就能够了。不信去查看node_modules下的.bin路径,里面确定有webpack这个js文件的。
使用npm脚本的优势不少,好比:
- 项目的相关脚本,能够集中在一个地方。
- 不一样项目的脚本命令,只要功能相同,就能够有一样的对外接口。用户不须要知道怎么测试你的项目,只要运行npm run test便可。
- 能够利用 npm 提供的不少辅助功能。
看到这里,我想你对前端构建演变之路和webpack有了一个基本的了解。接下来咱们会更深刻地去学习webpack中入口,出口,loader和plugins的具体用法。
entry就是webpack打包的入口,用来指示 webpack应该使用哪一个模块,来做为构建其内部依赖图的开始。webpack 会从入口找出有哪些模块和库是入口起点(直接和间接)依赖的。
为何咱们须要使用entry来找到全部须要打包的文件,这离不开webpack构建的方式。和其余构建工具不一样的是,webpack是一个模块打包器。使用webpack打包,它会把一切的资源(js、css、图片等文件)当成一个个的模块。而这些模块之间是存在依赖关系的,好比html页面引入js、css,好比某个组件引入另外一个组件等等。所以,webpack会根据入口文件去找到入口文件的依赖,而这些依赖也会依赖别的文件,这样就造成了如上图所示的依赖关系图。最后,将依赖关系图里的资源所有打包。
entry分为两种,单入口和多入口。单入口的entry是一个字符串,多入口的entry是一个对象。
单入口
module.exports = {
entry:'./src/index.js'
}
复制代码
多入口
module.exports = {
entry:{
index:'./src/index.js',
user:'./src/user.js'
}
}
复制代码
顾名思义,单入口适用于一个项目里只有一个入口文件,好比这个项目只有一个页面或者是一个单页应用。多入口适用用多页应用的场景。
entry定义了输入的资源,对应咱们的源代码,output则定义了输出的结果,对应转换后的代码。也就是说,咱们经过output来指定webpack编译打包后的文件的输出结果,具体到输出的目录和文件的名称。
单入口
const path = require('path')
module.exports = {
entry:'./src/index.js',
output:{
path:path.join(__dirname,'dist'),
filename:'bundle.js'
}
}
复制代码
多入口
const path = require('path')
module.exports = {
entry:{
index:'./src/index.js',
user:'./src/user.js'
},
output:{
path:path.join(__dirname,'dist'),
filename:'[name].js'
}
}
复制代码
单入口只须要配置输出的路径和文件名就能够了,多入口同理,并非说有多个入口,就要配置多个出口,无论有多少个入口,都只有一个出口。
多入口的状况下经过配置占位符来确保文件名称的惟一。filename里的[name]就是一个占位符,name就是文件打包出来的名称,举个具体的例子来看一下。
在刚才的src文件里新增一个user.js,在user.js里随意写一些代码 :
user.js
document.write(' userCenter page ');
复制代码
接着配置webpack.config.js为多入口的状况:
webpack.config,js
const path = require('path');
module.exports = {
entry:{
index:'./src/index.js',
user:'./src/user.js'
},
output:{
path:path.join(__dirname,'dist'),
filename:'[name].js'
},
mode:'production'
}
复制代码
执行npm run build,最终打包生成了两个文件。
webpack只能识别js和json两种文件类型,对于前端开发比较经常使用的css、less、vue、jsx等文件,是没法识别的。loader能够将这些类型的文件转换为webpack可以识别的有效模块,这样webpack才能把这些文件加入到打包的依赖图中。
loader的本质是一个函数,接收源文件做为参数,返回转换的结果。
下图展现了webpack中经常使用的loader
固然,官方的loader很是多,咱们须要用到的时候能够去官网查怎么使用,点击这里查看官方给出的loader
plugins的用法以下:
module.exports = {
// ...
module:{
rules:[
{ test: /\.txt$/, use: 'raw-loader' }
]
}
}
复制代码
loader配置在module里的rules里,rules是一个数组,里面能够配置多个loader。 在每个配置项里,经过test去设置匹配规则,经过use来指定当前使用的loader的名称。
loader 被用于转换某些类型的模块,而插件则能够用于执行范围更广的任务。好比:
通俗的讲,plugins的做用就是加强webpack的功能,让webpack能作更多的事,任何loader没法作到的事,均可以经过plugins去完成。 plugins做用于整个构建过程。
下图展现了经常使用的plugins:
固然,这只是一小部分plugins,更多插件能够去官网查看,点击这里查看官方给出的plugins
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 经过 npm 安装
module.exports = {
// ...
plugins:[
new HtmlWebpackPlugin({template: './src/index.html'})
]
}
复制代码
插件的用法很简单,要使用什么插件,就先用npm把这个插件安装好,而后再把这个插件配置到plugins这个数组里面去就行了。
mode用来指定当前构建的环境,有development、production和none三种类型。
设置mode的好处是,能够经过mode自动地触发webpack内置的函数,好比设置mode为development,那么webpack会自动开启在开发阶段的一些实用的参数。好比,process.env.NODE_ENV的值就会变成development
mode的默认值为production,若是不设置mode,webpack会自动开启在生产阶段的一些实用的参数和插件,若是将mode设置为none,那么webpack什么也不会帮咱们作。
下图展现了mode的内置函数功能:
module.exports = {
mode:'production' // 'development','none'
}
复制代码
。。。(待更新)