webpack简单搭建

1.初始化项目


1.创建项目并初始化package.json

npm init -y

2.安装webpack和webpack-cli开发环境工具

 npm i webpack webpack-cli -D

3.在项目根目录下创建默认的webpack配置入口文件webpack.config.js,该文件在执行webpack打包构建项目时会被执行,所有的loader和插件环境,运行环境配置都在配置文件中配置使用,若要修改默认配置文件的入口,可执行以下指令

webpack --config webpack.dev.config.js 
// webpack.config.js --> webpack.dev.config.js

4.在package.json文件中配置webpack命令脚本

"scripts":{
	"build": "webpack --config webpack.config.js"
}

在命令行执行如下命令,即可打包构建当前项目

npm run build 

2.Webpack配置入口和出口


webpack.config.js文件中,entry字段对应入口文件,output.path字段对应出口文件所在路径,output.filename对应出口文件名

// webpack.config.js
let path = require('path')

let config = {
  entry:'./src/main.js', // 入口文件
  output:{ // 出口
    path:path.resolve(__dirname,'dist'),
    filename:'bundle.js'
  }
}

module.exports = config

在根目录下执行以下指令,会将src/main.js作为入口文件开始编译打包输出为dist目录下的bundle.js文件

webpack

或者不用在webpack.config.js中配置entryoutput,在package.json文件中定义脚本(不推荐)

"scripts":{
    "build":"webpack --entry ./src --output-path ./dist"
}

执行如下命令即可

npm run build

3.Loader


前端工程化流程中使用loader实现预编译及其处理依赖关系的功能,通常是在webpack.config.js文件中的module.rules字段配置,主要是为了实现html、css、js等文件所不具备的特性,例如:

sass-loader less-loader
SASS LESS
CSS
babel-loader
ES6/7/8/9/10
JS
ts-loader
TS
JS
file-loader url-loader
图片 MP3 MP4等资源
JS

通过loader还可以将所有类型的文件都统一转化成模块,即 “万物皆模块”的前端工程化思想,从而可以根据ESModule或者CommonJS模块化规范引入任何类型的文件

// 1.ESModule规范
export { xx } // 导出
import xx from 'xx.js' // 导入
import 'yy.css' // 在js中执行yy.css里的代码,前提需要css-loader处理编译转换为js能读取的代码

// 2.CommonJS规范
module.exports = { xx } // 导出
let xx = require('xx.png') // 导入

3.1 css相关loader

1. css-loader

用于编译css文件,使其可以作为一个模块以import 'xx.css'的方式引入,首先需要安装css-loader

npm i css-loader -D

webpack.config.json文件中module.rules中配置对应的loader

 module:{ // 模块
    rules:[ // 打包规则
      {
        test:/\.css$/, // 正则匹配到的以.css结尾的文件
        use:['css-loader'] // 用css-loader预编译
        
       // use其他写法
       // use:[
       // 	{ loader:'css-loader'}
       // ]
       
       // loader:'css-loader' 若只有一个loader那么不用use
      }
    ]
  }

2.style-loader

用于将编译好的css文件以style标签的方式插入html文件中,安装 style-loader

npm i  style-loader

webpack配置

 module:{
    rules:[
      {
        test:/\.css$/,
        use:['style-loader','css-loader'] // 注意数组项的顺序,这里会先css-loader处理后再交给style-loader处理,这样css样式才会最终应用于html文件
      }
    ]
  }
3.less-loader和sass-loader

将less、sass文件编译成css文件,安装

npm i less-loader less sass-loader -D

补充:less插件可以将指定less文件转译为css文件,指令如下

npm less ./style/index.less ./style/index.css

但是这样是通过命令行手动一个一个编译的,不方便,故需要安装less-loader,通过webpack配置,让less-loader自动调用less插件实现自动编译。

配置

  module:{
    rules:[
      {
        test:/\.less$/,
        use:['style-loader','css-loader','less-loader'] // less --> css --> 插入style标签
      },
      {
        test:/\.(scss|sass)$/,
        use:['style-loader','css-loader','sass-loader']
      }
    ]
  }

3.2 JS相关loader

1.babel

question1:为什么需要babel?

answer:babel可以将jsx,ts,es6+,vue等转译为浏览器能够识别的js,并做兼容处理(根据配置的browserslist条件,browserslist配置参见 [3.3 兼容处理 1.浏览器兼容])

webpack的作用主要是根据依赖图递归打包项目,不会更改代码中除 import 和 export 语句以外的部分。如果使用其它 ES2015 特性,确保在 webpack 的 loader 系统中使用了一个像是 Babel 或 Bublé 的转译器

2.babel-loader

babel-loader是一个非常非常重要的编译JS的插件,其中@babel/preset-env提供了全套的预置功能例如@babel/plugin-transform-arrow-functions(转化箭头函数的插件),@babel/plugin-transform-block-function(转化const、let语法),用以增强babel-loader,安装

npm i babel-loader @babel/core @babel/preset-env -D

webpack配置

module: {
  rules: [
  	{
       test:/\.js$/,
       use:[
         loader:'babel-loader',
         options:{
		   //plugins:[
             //'@babel/plugin-transform-arrow-functions',
             //'@babel/plugin-transform-block-function',
           //]
           presets:['@babel/preset-env']
		 }
       ],
       exclude:/node_modules/ // 排除node_modules目录
     }
  ]
}
3.polyfill

polyfill即为填充,是做js兼容处理的,会根据js源码以及broserslist条件填充相应的代码,这些填充的东西通常@bable/preset-env不存在相关的功能可编译,所以是作为@bable/preset-env的一个补丁

例如源码中使用到Promise这个来自ES6的新玩意,而某些浏览器不会识别Promise这个对象,那么polyfill会在打包构建的代码中填充Promise对象的实现代码,

安装

npm i core-js regenerator-runtime -D

core-js/stable用于填充符合ES标准的新语法特性,regenerator-runtime用于编译ES6后新添加的迭代器方程

在.babel.config.js中配置

module.exports={
  presets:[
    [
      '@babel/preset-env',
      {
        useBuiltIns:'usage',
        // 默认为false,即不对js做填充处理
        // usage,根据兼容浏览器及JS源码,按需填充(推荐)
        // entry,根据兼容浏览器全填充,
        // 坑:若为entry需要在入口js文件开头导入core-js/stable和regenerator-runtime/runtime
        corejs:3, // 坑必须指定corejs版本
      }
    ]
  ]
}

或者在根目录下创建.babelrc文件,传入@babel/preset-env扩展babel-loader,此时无需再配置options.preset字段

{
  "presets": ["@babel/preset-env"] // 预处理
}

假设入口文件./src/index.js

const num = 1
const fn = ()=>{
  console.log(num);
}
fn()
4.vue-loader

vue-loader@15版本之前,配置

{
  test:/\.vue$/,
  use:['vue-loader']
}

vue-loader@15版本后,需要加入plugin

let VueLoaderPlugin = require('vue-loader/lib/plugin')

plugins:[
  new VueLoaderPlugin ()
]

入口文件

import Vue from 'vue'
import App from './APP.vue'
new Vue({ render:h=>h(App) }).$mount('#app')
5.ts-loader

ts-loader可以编译ts文件,但是推荐统一使用babel-loader,传入presets
即可,在babel.config.js中配置如下字段即可

module.exports={
  presets:[
    [ '@babel/preset-env',{
       useBuildIns:'usage',
       corejs:3
     } ],
    [ '@babel/preset-typescript' ]
  ]
}

3.3 兼容处理

1.浏览器兼容

在根目录下创建.browserslistrc文件,使得项目构建时能够根据文件中设置的条件,在使用loader时例如配置了预设babel-preset-envbabel-loader、配置了预设postcss-preset-envpostcss-loader,会根据此条件做兼容编译处理,

.browserslistrc配置如下

> 1% // 市场使用度超过1%的浏览器版本
last 2 version // 该浏览器最新的两个版本
not dead // 还未淘汰(两年之内有过更新)

推荐在package.json文件中配置browserslist字段,无需创建.browserslistrc文件

"browserslist":[
    ">1%",
    "last 2 versions",
    "not dead"
]

补充:webpack包下内置browserslist包,在执行webpack打包构建时可读取.browserslistrc文件; 在命令行执行如下命令会输出打印要兼容的浏览器版本列表

npm browserslist
2.postcss样式兼容

postcss是利用js转换css样式的工具,根据配置的browserslist条件自动补充css属性前缀,工作流程:

sass-loader less-loader
SASS LESS
CSS

安装postcss整体工具、postcss-cli命令行工具(可以不安装)、autoprefixer自动补充前缀插件(是扩展postcss-loader功能的插件)、postcss-loaderpostcss-preset-env(postcss-loader配置预设即插件结合)

npm i postcss postcss-cli postcss-loader postcss-preset-env autoprefixer -D

配置

{ 
    test: /\.css$/, 
    use: [
    'style-loader', 
    'css-loader',
    {
        loader:'postcss-loader', // 配置loader为使用postcss-loader
        options:{ 
            postcssOptions:{ // 配置postcss-loader选项
                plugins:[
                    // require('autoprefixer'), // 传入autoprefixer插件用于补充css属性前缀
                    // reuire('postcss-preset-env') // 预设已经包含了autoprefixer
                    'postcss-preset-env' // 简写
                ]
            }
        }
    }
	] 
}
// 注意:postcss-loader要在css-loader之前对css代码进行兼容处理,在把处理好的css文件传入给css-loader进行依赖处理,问题是在less、sass的loader配置中又得再CV一遍,冗余且耦合

推荐在根目录下创建postcss.config.js文件,并在文件下对postcss-loader进行全局配置

module.exports = {
  plugins: [
    require('postcss-preset-env')
  ]
}

这样一来,在use里只需传入’postcss-loader’即可,无需再一个一个地重复配置

3.babel

使用@babel/preset-env以及做polyfill填充即可


3.4 其他loader

1.import-loader

import-loader负责处理各模块之间的依赖关系,为什么需要这个loader呢?例如有一个index.css文件

// index.css
@import './common.css'
#app {
  color:pink;      
}

假如loader顺序为postcss ==> css ==> style,在postcss-loaderindex.css文件处理,并不会能解析@import './common.css'这个字段,所以只是处理了index.css里的css代码,然后到css-loader后可以解析@import './common.css'./common.css代码合并,最终结果是postcss-loader并未对./common.css进行处理,故需要在postcss-loader之前配置一个import-loader处理@import字段

配置如下:

use:[
      'style-loader',
      {
        loader: 'css-loader',
        options: {
          importLoaders:1 // 当有@import时将导入的文件退回上一个loader进行处理,具体数值看场景
          esModule:false // 解析background-img:url('xx.png')时,遇到url会将其作为esModule处理,设置关闭避免引入路径出错bug

      	}
      },
     'postcss-loader'
	]
2.file-loader

file-loader顾名思义就是文件资源的loader

const img = require('./assets/imgs/flower.png')
const music = require('./assets/medias/凤舞九天劲爆DJ.mp3')
const mp4 = require('./assets/medias/乡村爱情故事第三季1.mp4')

配置

{ test:/\.(png|gif|svg|jpe?g|mp3|mp4)$/,use:['file-loader']}

通过传入options管理打包后的资源名,

{
  /**
    * [ext]:扩展名
    * [name]:文件名
    * [hash]:文件内容
    * [hash:<length>]:限制hash长度
    */
	test: /\.(png|gif|svg|jpe?g|mp3|mp4)$/,
	use: [
      {
        loader:'file-loader',
        options:{
          // name:'[name].[hash:7].[ext]',
          // outputPath:'img' // 资源放置于img目录下
          name:'img/[name].[hash:7].[ext]'
        }
      }
    ]
}
3.url-loader

file-loader功能类似,但是url-loader可以将文件资源路径转化成base64的形式,从而减少网络请求,而file-loader是将资源打包至指定目录下,分开请求

补充:base64是将二进制文件以data uri字符串的形式来表示,也就是基于64个可打印字符来表示二进制数据,是网络上常用的用于传输8Bit字节码的编码方式

url-loader内部也可以调用file-loader,最佳实践配置如下

use: [ 
  {
    loader:'url-loader',
    options:{
      name:'img/[name].[hash:7].[ext]',
      limit:25*1024 // 小于25kb使用base64,否则调用file-loader
     }
  }
]
4.asset

asset是webpack内置的插件模块,可以替代file-loader、url-loader、raw-loader如下:

asset/resource --> file-loader
asset/inline --> url-loader
asset/source --> raw-loader
asset

配置

output:{
  // ...
  assetModuleFilename:'img/[name].[hash:7][ext]' // 指定资源存放路径及名称
},
module:{
  rules:[
    { 
      test: /\.(png|gif|svg|jpe?g|mp3|mp4)$/,
      type:'asset/resource' // 替代file-loader
    }
  ]
}

或者

{
     test: /\.(png|gif|svg|jpe?g|mp3|mp4)$/,
     type:'asset/resource',
     generator:{
       filename:"img/[name].[hash:7][ext]" // 指定资源存放路径及名称
     }
   }     

设置type为asset,最佳实践

{
   test: /\.(png|gif|svg|jpe?g|mp3|mp4)$/,
   type:'asset',
   generator:{
     filename:'img/[name].[hash:7][ext]'
   },
   parser:{
     dataUrlCondition:{
       maxSize:25*1024 // 小于25kb使用base64解析为data uri
     }
   }
 }
5.字体图标

字体图标文件通常后缀名为.ttf.woff,在css文件中通过@font-face的方式引入,例如

@font-face{
	font-family:'iconfont';
	src:url('iconfont.ttf?t=15123458') format('truetype'),
		url('iconfont.woff?t=15123458') format('woff'),
		url('iconfont.woff2?t=15123458') format('woff2');
}

但是前面提过css-loader处理url字段时把它当作esModule处理,此时需要指定对应的loader,可以使用asset/resource

配置

{
  test:/\.(ttf|woff2?)$/,
  type:'asset/resource',
  generator:{
    filename:'font/[name].[hash:7][ext]'
  }
}

4. Plugin


plugin(插件)可以在webpack打包构建项目的任何时机注入从而能做一些干预,贯穿webpack工作的整个生命周期,类似生命周期钩子。

plugins干预构建流程
项目源码
构建后的项目

通常需要安装后在webpack.config.js文件中引入对应插件构造函数,并在plugins数组字段中传入插件实例即可。

4.1 clean-webpack-plugin

clean-webpack-plugin可以在打包前自动删除打包后的目录,而无需每次手动删除

安装

npm i clean-webpack-plugin -D

配置

// 引入
let {CleanWebpackPlugin} = require('clean-webpack-plugin')

module.exports={
  // ...
  plugins:[ // 定义插件集合
    new CleanWebpackPlugin(), // 传入插件实例
  ]
}

4.2 html-webpack-plugin

html-webpack-plugin可以基于自己创建html模板在打包后的dist目录里动态添加index.html

1.配置

let HtmlWebpackPlugin = require('html-webpack-plugin')

plugins:[
  new HtmlWebpackPlugin({
	title:'项目名', // 字段值会在打包时传入<title></title>
    template:'./public/index.html', // 定义模板文件
  })
]

2.在public目录下创建html模板文件index.html,例如vue的html模板

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title><%= htmlWebpackPlugin.options.title %></title>
  <!-- 这里为ejx语法 -->
</head>
<body>
  <div id="app"></div>
</body>
</html>

3.执行打包构建命令npm run build,获得打包构建后的./dist/index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>项目名</title>
  <script defer="defer" src="index.js"></script>
</head>
<body>
  <div id="app"></div>
</body>
</html>

4.3 DefinePlugin

DefinePlugin是webpack内置的用于定义常量的插件(无须npm install了),通过键值对的方式定义

配置

let {DefinePlugin} = require('webpack')

plugins:[
  new DefinePlugin({
    BASE_URL:'"./"' 
    // 注意这里是一个坑,插件在编译时会原封不动地将'...'里的
    // 东西赋值给BASE_URL这个变量,因此传入字符串需要为'"./"'
    // 即 BASE_URL = "./",若为对象则传入格式为'{...}'
  })
]

vue默认的html模板为

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <link rel="icon" href="<%= BASE_URL %>favicon.ico">
  <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
  <div id="app"></div>
</body>
</html>

此时定义了BASE_URL这个常量,模板打包编译后的./dist/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <link rel="icon" href="./favicon.ico">
  <title>项目名</title>
  <script defer="defer" src="index.js"></script>
</head>
<body>
  <div id="app"></div>
</body>
</html>

4.4 copy-webpack-plugin

copy-webpack-plugin是webpack内置的插件,可自动将静态资源目录拷贝进打包后的目录里

配置

let CopyWebpackPlugin = require('copy-webpack-plugin')

plugins:[
 new CopyWebpackPlugin({
   patterns:[
     {
       from:'public',
      //to:path.resolve(__dirname,'dist') // 推荐省略to字段,默认会找webpack定义的出口path
       globOptions:{
         ignore:['**/index.html'] // 忽略不需要copy的文件
       }
     }
   ]
 })
]

5. 搭建本地服务器server


webpack配置watch字段为true,结合vs-code的live server服务器插件可以实现修改源码自动打包更新的效果,但是只修改一个地方,不能实现局部更新,wepack要重新再将所有源码打包编译,这种频繁读写文件的操作性能极差

5.1 webpack-dev-server

webpack-dev-server是webpack生态下的一个live server本地服务器,会将项目编译打包后存放在本地虚拟内存之中,默认为localhost:8080,即默认在本地服务器的8080端口

安装

npm i webpack-dev-server -D

package.json文件中配置启动指令webpack serve即可

"scripts": {
  "serve":"webpack serve"
},

运行以下命令,并访问localhost:8080

npm run serve 

在webpack配置文件中,对应devServer字段可配置服务器

5.2 webpack-dev-middleware

webpack-dev-middleware可以将webpack打包构建后的文件作为中间件传递给一个自定义的服务器

安装

npm i webpack-dev-middleware -D

结合express开启服务器使用,在根目录下创建一个serve.js文件

let express = require('express')
let webpackDevMiddleware = require('webpack-dev-middleware')
let webpack = require('webpack')

const config = require('./webpack.config') // 获取配置文件
const compiler = webpack(config) // 编译打包

const app = express() // 创建server服务
app.use(webpackDevMiddleware(compiler)) // 作为中间件传递
app.listen(3000,()=>{
  console.log(`服务器运行在: localhost:3000`);
})

执行命令,开启服务后,访问localhost:3000即可

node serve.js

5.3 HMR

HMR即Hot Module Replacement,模块热更新,可以实现局部模块更新,其他的原封不动,极大地提升了构建性能。

HMR是基于本地开发服务器实现的即webpack-dev-server,对于webpack-dev-middleware将项目打包作为中间件传递给其他服务器,要实现HMR的办法目前我还不知道

配置

// 在webpack.config.js文件下配置devServer字段即可
devServer:{
  hot:true
}

指定热更新模块

// ./src/a.js
function fn() {
  console.log('a');
}
export{
  fn
}

// ./src/b.js
function fn2() {
  console.log('b');
}
export{
  fn2
}

// ./src/index.js
import { fn } from "./a";
import { fn2 } from "./b";

fn()
fn2()

if(module.hot){ // 如果开启热更新
  module.hot.accept(['./a']) // 热更新a.js文件,修改b.js则重新编译而非热更新(页面会重新刷新)
}

5.4 path

output为出口路径配置,属性配置如下:

path: 打包目录
filename: 出口文件名
publicPath:index.html内部引用路径= 域名+publicPath + filename
assetModuleFilename: asset-loader打包资源后存放地址及命名

项目构建打包部署到服务器后,域名解析即为打包后的项目根目录(默认为dist)

devServer下的publicPath指定的是本地服务所在的目录,默认为dist上一级即源码根目录

resolve定义webpack对路径的解析规则

resolve:{
  extensions:['.js','.json','.ts','.vue'], // 识别扩展名
  alias:{
    '@':path.resolve(__dirname,src) // 路径别名
  }
},

5.5 Proxy代理配置

proxy可以在开发阶段解决跨域问题,因为服务器与服务器之间不存在跨域的问题,proxy核心思想就是基于devServer服务器作为客户端与接口服务器之间的代理转发,从而实现跨域,因为devServer与客户端是同源的

Proxy
Proxy
客户端
服务器

配置

devServer:{
  hot:true, // 开启热更新
  port:3000, // 项目部署于本地的3000端口
  proxy:{
    '/api':{
      target:'https://api.somewhere.com', // 用'/api'代理target
       // 若访问/api/users会代理的是https://api.somewhere.com/api/user
      // 而实际想要访问的是https://api.somewhere.com/users,那么需要重写覆盖
      pathRewrite:{'^/api':''}
      changeOrigin:true // 改变请求头里的host,设置为target同名的,以实现同源
    },
    '/api2':{
      target:'https://api.somewhere2.com'
    }
  }
}

5.6 source-map

source-map即源码映射,使得开发时能直接定位到源码,方便调试

在webpack配置文件中定义devtool字段即可

devtool:'source-map'

会在构建项目时生成index.js.map文件,是出口文件的一个map映射

5.7 配置区分开发环境和生产环境

配置package.json文件里的script字段

 "scripts": {
   "build": "webpack --env prd",
   "serve:dev":"webpack serve --env dev",
   "serve:prd":"webpack serve --env prd"
 },

之后在执行脚本构建项目时,会将prd或者dev属性挂载到prossess.env对象上,可以在webpack.config.js文件中访问到env变量

// 方法1
let { prd: isPrd,dev: isDev} = prossess.env

// 方法2
module.exports = (env)=>{
  let { prd: isPrd,dev: isDev} = env
}

// 下面展示如何根据环境区分配置
let path = require('path')
let { merge } = require('webpack-merge')

let defaultConfig = {
	// 默认配置
	entry:'./src/index.js',
	output: {
		path: path.resolve(__dirname, 'dist'),
		filename:'index.js'
	},
	module: {
		rules: [
			// ...
		]
	},
	plugins: [
		// ...
	]
}
let devConfig = {
	// 开发环境配置
}
let prdConfig = {
	// 生产环境配置
}
module.exports = (env) => {
	let { prd: isPrd, dev: isDev } = env
	let config;
	if(isDev) config = merge(defaultConfig,devConfig)
	if (isPrd) config = merge(defaultConfig, prdConfig)
	return config
}

6. resolve


该选项用于配置模块如何解析,即在执行require('xx')或者import xx from 'xx'语句时会使用到该规则

配置

resolve: {
  alias: { // 路径别名
    Utilities: path.resolve(__dirname, 'src/utilities/'),
    Templates: path.resolve(__dirname, 'src/templates/')
  },
  extensions: ['.js', '.json'], // 文件拓展 优先级从左到右
  modules: [path.resolve(__dirname, 'src'), 'node_modules'] // 默认为['node_modules'],规定webpack解析模块时应该搜索的目录
  mainFields: ['browser', 'module', 'main'], // 规定要解析模块目录下package.json文件中的哪个字段为模块路径
}

// loader解析去掉后缀
resolveLoader: {
  moduleExtensions: ['-loader']
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值