打包是一个将文件引入并合并到一个单独文件的过程,最终形成一个 “bundle”。 接着在页面上引入该 bundle,整个应用即可一次性加载
一、了解webpack
webpack是一个模块打包器。是在开发阶段使用,开发完了之后上线这些就和webpack一点关系都没有了
浏览器现在还是不支持es6的import这些语法的,所以要把它去交给webpack来处理
安装webpack的依赖:
npm i // 如果在package.json中写了一些依赖的版本,运行这个命令会自动帮我们添加那些版本上
npm init -y // 初始化一个package.json文件。直接用vscode的终端好像不行,要用git的终端或者cmd
// 安装项目开发的依赖webpack,安装完了之后就会有一个node_modules文件夹了(这个文件夹一般是不动它的),i为install的简写
npm i -D webpack // npm i -D webpack@3 这个是安装3版本的webpack
npm i -D webpack-cli // 在webpack 4 之上的版本,需要安装 webpack-cli 不然会报错
二、配置webpack
里面的dev应该指的是development
1、webpack安装插件
- 安装了插件,如果用的是-S(–save)后缀,则会在package.json的的dependencies(生产阶段依赖)属性中加入插件的信息;如果用的是-D(–save-dev)后缀,则会在package.json的devDependencies属性中加入插件的信息。要使用这个插件需要到webpack.config.js的plugins中去配置。
- html-webpack-plugin: 这个插件默认会自动在打包的路径下去生成一个index.html文件(默认是没有的),这个index.html会自动帮我们引入js。当然调用这个插件的时候依然是可以接收一些参数的。
- filename: 生成的文件的名字,当然也可以给生成的文件加上一些路径的信息
- template: 指定模板的文件的路径,这个插件会在模板的基础上加东西(也就是说,模板的东西会保留)。
npm i -D html-webpack-plugin
new HtmlWebpackPlugin({
filename: '../index.html', // 生成的名字为index.html,且在输出位置的上层路径,比如输出路径为dist/assets,那么输出之后index.html就和assets在同一目录,不过一般是只用index.html的
template: 'src/index.html' // 模板是src下面的index.html文件
})
- babel-loader babel-core: 这两个是一起安装的,用来把浏览器不支持的代码,编译成支持的。babel的一些配置(比如用什么插件,用什么预设之类的)可以不到module里面去声明,而写在.babelrc文件中,运行的时候会自动到.babelrc文件中去找
npm i -D babel-loader@7 babel-core
- postcss-loader css-loader style-loader: postcss-loader用来处理css的兼容性,比如加上前缀这些。css-loader用于可以在文件中使用import的方式引入css。打包之后再用style-loader用来把打包好的css样式插入dom结构里面去
npm i -D postcss-loader
npm i -D css-loader
npm i -D style-loader
- sass-loader node-sass: 把sass打包成模块
npm i -D sass-loader node-sass
- less-loader less: 把less打包成模块,用法和sass一样的
npm i -D less-loader less
- file-loader: 这个是用来把文件(图片、字体文件)打包成模块的。这个主要是打包css中url里面的那些图片和在js中用模块的方式导入图片,比如
background: url(img.jpg)
, html中的图片就没办法了
$ npm i -D file-loader
- url-loader: 这个为file-loader的加强版,这个就不把图片打包成路径了,而是把图片进行编码,编码之后就成了
base64编码的data:url
这种字符串(而网页是可以识别这种字符串的),所以把页面请求回来的时候,这个图片自然就有了(因为图片已经被编码成了字符串嵌套在页面中了),就减少了网络请求。但是,如果图片过大,字符串就很长,这种方法就不好了,就需要用file-loader去打包
$ npm i -D url-loader
- font-awesome: 这个font-awesome首先是一个字体图标库https://fontawesome.com/,然后要用这个库的字体图标的话,就需要安装这个。安装好了之后,这个就会在node_module里面有个模块
$ npm i -S font-awesome
- devserver: 这个为webpack里面用的服务器,安装了就可以用了。使用这个是到package.json的scripts里面去添加命令,配置服务器当然还是到webpack.config.js里面去
npm i -D webpack-dev-server
2、webpack中的loader
loader就是webpack用来预处理模块的,在一个模块被引入之前,会预先使用loader来处理模块内容,把浏览器不能直接识别的内容转成浏览器可以直接识别的内容。比如,把es6代码编译成es5语法,那个babel在这里面就是一种loader
每个模块被打包后,都会生成一个模块id,加载到main.js里面去。然后,比如图片被打包了,图片的名字就不是他以前那个名字了,而是一个路径
3、webpack中的devserver
这个为webpack的开发服务器,是webpack专门用来帮我们调试项目的。这样就可以让我们的项目跑在服务器上,然后通过请求来访问我们的项目。
ctrl+z退出服务器
运行服务器之前,它会先自动帮我们打包(就像输入打包命令那样),不过它不会把文件直接打包出来,而是放到内存里面,所以我们是不会在打包的输出目录里面找到打包出来的文件的,然后这个服务器运行时先到内存中找文件,没有的话再到我们指定的目录去找文件。服务器的端口号会直接显示在运行的那些代码里面。
这个东西会监视文件的变化,比如我开启了服务器之后,再改变我的文件,那么页面会自动刷新(不用手动刷新)
4、webpack中一些node.js的内置模块
(1)path
首先需要引入模块,然后才可以使用
- path.resolve(): 表示使用path模块
- 第一个参数为__dirname代表文件跑起来的根目录,
- 第二个参数为运行到这个根目录下的哪个文件夹
const path = require('path'); // 引入
path.resolve(__dirname, 'dist'); // 使用
5、webpack中一些文件的配置
(1)package.json文件
这个就是整个项目的配置文件了。配置这个文件一定要小心!!!!!!!!它的格式多一个空格都要出错,一定不要乱加注释!!!!!!!!!!!!
注:下面这些很多都是相对自定义命令来说的。
- scripts: 想要运行的命令就在这个属性后面去声明。声明方式为前面为声明的命令的名字,后面为表示运行声明的命令的时候会运行哪些实际的东西
- npm run dev:就可以运行新建的那个dev命令(实际上这个为打包命令)。运行了之后,就会调用指定的webpack配置文件,然后在指定的文件夹生成指定的文件,即打包到那个文件去
- npm run start: 输入这个就可以运行后面的webpack-dev-server服务器了。命令后面那个
webpack-dev-server
,如果直接这样写的话它是会找webpack.config.js配置文件的,如果我们已经在上面修改了,那么在这里也需要修改。
{"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
// 下面这条就是新加的命令,表示命令的名字为dev,要运行的为webpack命令
// webpack命令默认会调用webpack.config.js文件
// "dev": "webpack", 这个为调用默认文件的配置
// 下面这个为修改webpack默认调用的文件
// 运行npm run dev之后就会调用webpack.config.dev.js文件
"dev": "webpack --config=config/webpack.config.dev.js" //这个config文件 是在config文件夹中的
//这个命令为devserver服务器的命令
// "start": "webpack-dev-server" 默认命令,devserver找的配置文件是dev.config.js
// 由于我们修改了webpack默认的配置文件,所以这里也需要修改
"start": "webpack-dev-server --config webpack.config.dev.js"
}}
(2)webpack.config.js
这个就是webpack的配置文件。导出一个对象,这个对象就是webpack的配置了,描述了webpack的一切,会以怎样的方式打包,打包中会干什么,都是这里说明。。由于这个文件是用node.js来跑的,所以这里就需要用node.js的语法,而不是用es6的import
- mode: 指定webpack使用的模式。
- development: 开发模式,不会压缩代码
- production: 生产模式,要压缩代码
- entry: 这个属性后面跟的是程序的入口位置。值可以字符串、数组、对象,可以为相对路径
- 一般情况下是main.js文件
- output: 这个为一个对象。告诉webpack以怎样的方式进行打包,且输出什么东西。
- path属性:表示输出的文件的路径,即资源的打包路径。值为一个绝对路径(可以用path模块来指定),相对路径会报错
- filename属性:表示输出的所有js文件的名字和路径。值为字符串
- publicPath属性:这个代码中所有路径的基础路径,在所有资源的输出路径前面加上这个属性的那个字符串。比如,这个属性值为
assets/
,output的filename为js/main.js,最后代码中引入js的那个路径就为assets/js/main.js
。但是,一般来说就用个/就好了- 打包之后的文件路径设置:默认的情况下,所有的文件打包之后都会放到path声明的那个路径下面去的,但是这样就很乱,如果我们要到输出目录那里去弄些文件夹的话就需要自己配置。配置的方法是:找到编译文件的那个load或者插件或者output,然后这些东西的name或者filename就是输出后文件的路径(当然还是相对path那个路径的)加上文件的名字,但是这样文件在代码中的引入路径就是我们自己写的这个路径了,不会加上其它东西(所以就会引入资源出错)。
- plugins: 这个属性为一个数组,里面的为需要使用的插件。插件需要先用require引入,然后再在数组里面添加插件。添加插件的方法为new这个插件,比如
new HtmlWebpackPlugin
- module: 这个属性为一个对象,是用来配置loader的
- rules: 这个为一个数组,数组中的每一项为规则对象。规则对象的参数如下:
- test: 这个为一个条件,用来匹配到要处理的模块(文件)。可以用正则匹配
- use: 这个为一个规则,表示匹配到了要处理到的文件之后,用什么样的规则(loader)来处理这个文件。这个为一个数组,数组的每一项为loader对象(如果只需要使用这个loader的默认配置的话,就只需要写这个loader的字符串就好了,就不用配置),loadder对象的参数如下:
- loader: 要使用的loader的名字
- options: 这个用来配置上面的loader。值也为一个配置对象:
- limit: 这个为url-loader的options的配置。值为一个数值,为对图片的最大值进行限制。比如:
limit:10000
,就为10000b(这个b为bit)之下的图片就用这个配置- module: 这个为css-loader的配置。表示为是否开启css模块化,默认为false。
- name: 这个为file-load的配置。表示生成后的文件的名字
- exclude: 这个为一个排除数组。即,数组中指定的文件或者文件夹都不运用这个规则。
- include: 这个为一个使用数组。即,只有数组中指定的文件或者文件夹才运用这个规则。和exclude是相反的
- devServer: 这个属性为一个对象,用来配置devServer(webpack服务器)的
- open: 值为一个boolean值,即需不需要服务器启动完了之后,自动帮我们打开页面
- port: 值为一个数字,即需要占用的端口号
- contentBase: 这个值为一个路径,即devServer在本地找文件的时候,找本地文件的那个路径。默认为dist
// webpack.config.js
const path = require('path');
// 引入插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = { // 导出一个对象
mode: 'development',
entry: './src/app.js',
output: {
// 这个运行了npm run dev之后就会把所有的东西打包到dist/文件夹下
// 如果没有dist会自动创建
// 多次运行会覆盖以前的main.js文件
path: path.resolve(__dirname, 'dist/'),
filename: 'assets/js/main.js', // 这个为所有js的名字和路径
publicPath: '/' // 如果,指定代码中所有资源的基础路径为assets/,给所有资源代码中的路径前面加上这个字符串,比如上面那个代码中的路径就变为了assets/js/main.js
},
// 下面这个为使用插件
plugins: [
new HtmlWebpackPlugin({
filename: 'index2.html', // 一般html的输出路径是不变的
template: 'src/index.html' // 也可以写成path.resolve(__dirname, 'src/index.html')
})
],
module: {
rules: [{
test: /\.js/, // 即匹配所有的js文件
use: [{
loader: 'babel-loader', // 使用babel-loader
options: {
presets: ['react'], // 用react预设来处理react语法内容
plugins: ['transform-object-rest-spread']
}
}],
exclude: [path.resolve(__dirname,'node_modules')] // 排除node_modeles文件夹中的js文件(排除了之后性能要好些),因为它里面的不需要用这些
},
{
test: /\.css$/,
use: ['style-loader',
{
loader: 'css-loader',
module: false // 关闭css模块化
}],
exclude: [
path.resolve(__dirname,'src/common')
],
include: [
path.resolve(__dirname,'src/common')
]
},
{
test: /\.s(a|c)ss$/,
use: ['style-loader','css-loader','sass-loader',]
},
{
test: /\.(jpg|ttf|eot|woff|woff2|svg)$/, // ttf|eot|woff|woff2都为字体文件
use: [{
loader: 'file-loader',
// name: 'what' 这个就为匹配到的所有文件都输出这个名字,下面那个就是动态匹配
// [name]为文件的名字,[hash:8]为去文件的8位hash编码长度,.[ext]为匹配到的文件的后缀名
// name: '[name]_[hash:8].[ext]'
name: 'assets/font/[name]_[hash:8].[ext]' // 这个就是把文件打包后,在打包目录再新建一个font文件夹,把文件放到font文件夹下面
}]
},
{
test: /\.(ttf|jpg|jpeg|png|gif)$/,
use: [{
loader: 'url-loader',
options: {
limit: 10000, // 小于10000b的图片就用这个配置,被编码成字符串
name: 'assets/img/[name]_[hash:8].[ext]'
}
}]
}]
},
devServer: {
open: true,
port: 9000
}
};
(3)main.js文件
这个文件一般来说就是程序的入口文件,由于webpack中所有的资源都被认为是个模块,所以这个js文件中也可以引入其他类型的文件,比如.css文件
// main.js
import React from 'react'
import ReactDom from 'react-dom'
import './common/style/main.css'; // 只是引入,不使用的话,就可以不声明前面那个变量
import Hammer from './common/img/Hammer.gif'; // 引入图片,因为图片也是模块(实际上是使用file-loader进行打包或者用url-loader把图片进行编码), 这个Hammer变量(为一个路径或者一个编码),就代表这个图片了
import 'font-awesome/css/font-awesome.css' // 这个为引入font-awesome的字体图标,直接用font-awesome开头路径就好了,因为它是个模块
const img = `<img src=${Hammer}>`;
6、babel
babel是一个js编译器,比如可以把es6代码编译成es5代码。babel官网http://babeljs.io/
1、使用babel
- babel使用
npm init // 首先还是要初始化一个package.json文件出来
npm -i -D babel-cli // -D命令是--save-dev的简写
npm -i -g babel-cli // 这个命令是把babel-cli安装到全局
- package.json
// package.json
{"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
// src/app.js为需要用babel编译的那个文件路径
// -o代表后面是输出路径,out/js为编译后的js路径
"babelgo": "babel src/app.js -o out/a.js"
}}
- .babelrc: 这个文件是babel的插件,babel运行package.json的时候,就会在这里找需要哪些插件
- 预设(preset):就是把所有相关的插件弄到一起就是一个预设。因为如果一个一个的引入插件太麻烦了,所以就直接引入预设
- babel-preset-env: 这个预设代表兼容所有的es已经在规范里面的新特性预设,包括es2015,es2016等
- babel-preset-es2015: 这个预设代表是用es2015特性
- babel-plugins-transform-object-rest-spread: 这个插件用来支持还不在规范里面的es语法
// .babelrc
{
"plugins": ["transform-object-rest-spread"], // 这个为需要使用的插件
"preset": ["env", "es2015"] // 这个为需要使用的预设
}
三、webpack问题解决
- (1)清楚产生的log文件:
npm cache clean
- (1)关闭8080端口:输入
netstat -ano|findstr 8080
查看占用8080端口的进程,
// 查看占用8080端口的进程,就会出现一个进程号
netstat -ano|findstr 8080
// 运行windows自带taskkill命令,将上面显示的进程号,结束掉, 4708为进程号
taskkill /pid 4708 /f
四、完整webpack配置
文件结构如下:
- config
- webpack.common.js
- webpack.dev.js
- webpack.prod.js
- src
- css
- a.css
- img
- go.png
- index.js
- package.json
插件安装:
// 基础插件
npm init -y // 初始化一个package.json文件。直接用vscode的终端好像不行,要用git的终端或者cmd
// 安装项目开发的依赖webpack,安装完了之后就会有一个node_modules文件夹了(这个文件夹一般是不动它的),i为install的简写
npm i -D webpack // npm i -D webpack@3 这个是安装3版本的webpack
npm i -D webpack-cli // 在webpack 4 之上的版本,需要安装 webpack-cli 不然会报错
// loader
npm i -D babel-loader@7 babel-core // 解析es6
npm i -D postcss-loader css-loader style-loader // 打包css
npm i -D file-loader url-loader // 打包图片
// 其余插件
npm i -D webpack-dev-server // 用于开启服务器
npm i -D html-webpack-plugin // 用于打包进html
npm i -D webpack-merge // 用于代码拆分
npm i -D clean-webpack-plugin // 用于每次打包的时候清除上一次 output文件夹
npm i -D mini-css-extract-plugin // 抽离压缩css用的,替换style-loader
npm i -D terser-webpack-plugin // 抽离压缩css用的,替换style-loader
npm i -D optimize-css-assets-webpack-plugin // 抽离压缩css用的,替换style-loader
package.json:
{
"name": "lyTrip-copy",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server --config config/webpack.dev.js", // 开发环境,使用 webpack-dev-server 来启动服务器
"build": "webpack --config config/webpack.prod.js" // 生产环境,使用 webpack 来进行打包
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^4.3.0",
"file-loader": "^6.1.0",
"html-webpack-plugin": "^4.4.1",
"mini-css-extract-plugin": "^0.11.2",
"optimize-css-assets-webpack-plugin": "^5.0.4",
"postcss-loader": "^4.0.2",
"style-loader": "^1.2.1",
"terser-webpack-plugin": "^4.2.1",
"url-loader": "^4.1.0",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^5.1.4"
}
}
webpack.common.js: 里面只配置 基本的 入口、babel-loader 和 htmlWebpackPlugin
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: {
index: path.resolve(__dirname, "../src/index.js"),
// uploadError: path.join(srcPath, 'uploadError.js'),
// advertisement: path.join(srcPath, 'advertisement.js')
},
module: {
rules: [
{
test: /\.js$/,
loader: ["babel-loader"],
include: path.resolve(__dirname, "../src"),
exclude: /node_modules/,
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../index.html"),
filename: "index.html",
}),
],
};
webpack.dev.js:开发环境的配置,只配置 devServer 、css的loader 和 图片的loader
const path = require("path");
const webpackCommonConf = require("./webpack.common.js");
const { merge } = require("webpack-merge");
module.exports = merge(webpackCommonConf, {
mode: "development",
module: {
rules: [
{
test: /\.css$/,
loader: ["style-loader", "css-loader", "postcss-loader"], // 加了 postcss。loader 的执行顺序是:从后往
},
{
test: /\.(png|jpg|jpeg|gif)$/,
use: "file-loader", // 直接引入图片 url
},
],
},
// plugins: [new webpack.DefinePlugin({ENV: JSON.stringify('development')})],
devServer: {
port: 8080,
progress: true, // 显示打包的进度条
contentBase: path.resolve(__dirname, "../dist"), // 根目录
open: true, // 自动打开浏览器
compress: true, // 启动 gzip 压缩
proxy: {},
},
});
webpack.prod.js:生产环境配置,配置 css拆分、图片url-loader 和 js的代码拆分
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const webpackCommonConf = require("./webpack.common.js");
const { merge } = require("webpack-merge");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const TerserJSPlugin = require("terser-webpack-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = merge(webpackCommonConf, {
mode: "production",
output: {
filename: "bundle.[contentHash:8].js", // 打包代码时,加上 hash 戳
path: path.resolve(__dirname, "../dist"),
// publicPath: distPath // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
publicPath: "./static/",
},
module: {
rules: [
{
test: /\.css$/,
// loader: ['style-loader', 'css-loader', 'postcss-loader'] // 加了 postcss。loader 的执行顺序是:从后往
loader: [
MiniCssExtractPlugin.loader, // 注意,这里不再用 style-loader
"css-loader",
"postcss-loader",
],
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: "url-loader",
options: {
limit: 1, //如果大于或等于2048Byte,则按照相应的文件名和路径打包图片;如果小于2048Byte,则将图片转成base64格式的字符串。
name: "[name]_[hash:8].[ext]",
outputPath: "image",
},
},
],
},
],
},
plugins: [
new CleanWebpackPlugin(), // 会默认清空 output.path 文件夹
// new webpack.DefinePlugin({ENV: JSON.stringify('production')}),
new MiniCssExtractPlugin({ filename: "css/main.[contentHash:8].css" }), // 抽离 css 文件
],
optimization: {
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})], // 压缩 css
// splitChunks: { // 分割代码块
// chunks: 'all', // initial 入口 chunk,对于异步导入的文件不处理。async 异步 chunk,只对异步导入的文件处理。all 全部 chunk
// cacheGroups: { // 缓存分组
// vendor: { // 第三方模块
// name: 'vendor', // chunk 名称
// priority: 1, // 权限更高,优先抽离,重要!!!
// test: /node_modules/,
// minSize: 0, // 大小限制
// minChunks: 1 // 最少复用过几次
// },
// common: { // 公共的模块
// name: 'common', // chunk 名称
// priority: 0, // 优先级
// minSize: 0, // 公共模块的大小限制
// minChunks: 2 // 公共模块最少复用过几次
// }
// }
// }
},
});
文件代码如下:
// index.html
<!DOCTYPE html> <html class="no-js">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="./src/css/a.css">
</head>
<body>
<div class='go'>this is a demo<div class='go2'></div></div>
<script src="./src/index.js" async defer></script>
</body>
</html>
// index.js
import './css/a.css';
import img from './img/go.png';
window.onload = function() {
console.log('aaaaa');
console.log(css);
console.log(img);
const go = document.querySelector('.go2');
go.appendChild(`<img src="${img}" alt=""/>`);
}
// a.css
.go {background: url(../img/go.png) no-repeat;}