Webpack 基础篇 (三)

前言

需要把es6 转化成 es5,涉及到 有些api 不是es6语法,装饰器、类的属性…

babel 转化功能 vue-cli 基于babel6来实现的
现在的讲是babel7

1. 处理js模块

1.1 将 es6 编译成 es5

1
npm install @babel/core @babel/preset-env babel-loader --save-dev

@babel/core是babel中的核心模块,@babel/preset-env 的作用是es6转化es5插件的插件集合,babel-loaderwebpackloader的桥梁。

1
2
3
4
// es6 语法
const sum = (a, b) => {
return a + b;
};

webpack.base.js

1
2
3
4
{
test: /\.js$/,
use: 'babel-loader' //babel-loader默认会调@babel-core(可以直接在这里设置options,如果配置内容太多 也可以新建配置文件.babelrc)
},

新建 .babelrc , 这个文件就是options里面的内容

1
2
3
4
5
6
{
"presets": [ // 这里的执行顺序是从下往上
"@babel/preset-env"
],
"plugins": [] //这里也可以写其他的插件,执行顺序是从上往下
}

1.2 解析装饰器

1
npm i @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators --save-dev

.babelrc

1
2
3
4
5
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }], // 解析装饰器
["@babel/plugin-proposal-class-properties",{"loose":true}] // 解析类的属性
]
// 这些都是实验性语法 查看用法 https://babeljs.io => 搜索

legacy:true表示继续使用装饰器装饰器,loose为false时会采用Object.defineProperty定义属性

  • Plugin会运行在Preset之前
  • Plugin 会从第一个开始顺序执行,Preset则是相反的

1.3 corejs 替代 polyfill(已废弃)

示例
index.tsx

1
[1,2,3].includes(1) // 这个是es7语法

npm run build 打包 ,没有转化

默认不能转化高级语法 实例上的语法 promise(api也不转化)

或者是 根据.browserslistrc文件,转化使用到的浏览器api
解决
.babelrc

1
2
3
4
5
6
7
"presets": [
["@babel/preset-env",{
// 使用的api会自动转化
"useBuiltIns":"usage", // 按需加载
"corejs":2 // corejs 替代了以前的pollyfill
}]
]

安装corejs

1
npm install core-js --save

1.4 transform-runtime

使用transform-runtime
A plugin that enables the re-use of Babel’s injected helper code to save on codesize.可以帮我们节省代码

1
npm install --save-dev @babel/plugin-transform-runtime
1
npm install --save @babel/runtime

.babelrc中配置插件,完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"presets": [ // 这里的执行顺序是从下往上
["@babel/preset-env",{
// 使用的api会自动转化
"useBuiltIns":"usage", // 按需加载
"corejs":3 // 3代表版本号 corejs 替代了以前的pollyfill
}]
],
"plugins": [//这里也可以写其他的插件,执行顺序是从上往下
["@babel/plugin-proposal-decorators", { "legacy": true }], // 解析装饰器
["@babel/plugin-proposal-class-properties",{"loose":true}], // 解析类的属性
"@babel/plugin-transform-runtime"
]
}

1.5 添加eslint

安装eslint

1
2
npm install eslint
npx eslint --init # 初始化配置文件

2.source-map

  • eval 生成代码 每个模块都被eval执行,每一个打包后的模块后面都增加了包含sourceURL
  • source-map 产生map文件
  • inline 不会生成独立的 .map文件,会以dataURL形式插入
  • cheap 忽略打包后的列信息,不使用loader中的sourcemap
  • module 没有列信息,使用loader中的sourcemap(没有列信息)
1
devtool:isDev?'cheap-module-eval-source-map':false
1
2
3
4
5
{
test:/\.js/,
enforce:'pre',
use:'eslint-loader'
},

配置eslint-loader可以实时校验js文件的正确性,pre表示在所有loader执行前执行

Webpack中的sourcemap

3.resolve解析

想实现使用require或是import的时候,可以自动尝试添加扩展名进行匹配

1
2
3
resolve: {
extensions: [".js", ".jsx", ".json", ".css", ".ts", ".tsx", ".vue"]
},

4.拷贝静态文件

有些时候在打包时希望将一些静态资源文件进行拷贝,可以使用copy-webpack-plugin

安装插件

1
npm i copy-webpack-plugin --save-dev

5.配置TS环境

5.1 使用ts-loader

使用ts需要安装ts相关配置

1
npm install typescript ts-loader --save-dev

生成ts的配置文件

1
npx tsc --init

配置ts-loader

1
2
3
4
5
{
test:/\.tsx?/,
use: ['ts-loader'],
exclude: /node_modules/
}

将入口文件更改成ts文件

1
2
let a:string = 'hello';
console.log(a);

执行npm run dev发现已经可以正常的解析ts文件啦!

5.2 使用 preset-typescript

不需要借助typescript

1
npm install @babel/preset-typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"presets": [
["@babel/preset-env",{
"useBuiltIns":"usage",
"corejs":2
}],
"@babel/preset-react",
["@babel/preset-typescript",{
"allExtensions": true
}]
],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties",{"loose":true}],
"@babel/plugin-transform-runtime"
]
}

6.配置ts+react环境

安装react相关模块

1
npm install react react-dom --save
1
npm i @babel/preset-react --save-dev # 解析jsx语法

配置 .bablrc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"presets": [
["@babel/preset-env",{
"useBuiltIns":"usage",
"corejs":2
}],
"@babel/preset-react" // 顺序从下往上 先走解析react
],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties",{"loose":true}],
"@babel/plugin-transform-runtime"
]
}

index.tsx

1
2
3
4
import React from 'react';
import ReactDom from 'react-dom';

ReactDom.render(<h1>hello</h1>,document.getElementById('root'));

运行 npm run dev
访问 http://localhost:3000/ 页面显示hello

typescript

校验类型
index.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from 'react';
import ReactDOM from 'react-dom';
// ts 校验类型
interface IProps{
num:number
}
let initState = {count:0};
type State = Readonly<typeof initState>
class Counter extends React.Component<IProps,State>{
state:State = initState;
handleClick = ()=>{
this.setState({count:this.state.count+1})
}
render(){
return <div>
{this.state.count}
<button onClick={this.handleClick}>点击</button>
</div>
}
}
ReactDOM.render(<Counter num={1}/>,document.getElementById('root'));

解析ts的方案
1、ts-loader + typescript库
2、babel7下的 @babel/preset-typescript

1
npm i @babel/preset-typescript --save-dev

将 webpack.base.js中 入口文件 '/src/index.js' 改成 '/src/index.tsx'
module 里的 rules 再添加一项:

1
2
3
4
{
test: /\.tsx?$/,
use: 'babel-loader'
},

.babelrc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"presets": [
["@babel/preset-env",{
"useBuiltIns":"usage",
"corejs":2
}],
"@babel/preset-react",
"@babel/preset-typescript" // ****加了这行 先把ts转成js
],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties",{"loose":true}],
"@babel/plugin-transform-runtime"
]
}

运行 npm run dev 可以看到 页面上生成一个按钮,点击按钮数字+1

安装typescript 校验代码

1
npm install typescript

生成 typescript 配置文件

1
tsc --init

生成 tsconfig.json 文件
这时看 index.tsx 可以看到有许多保存,需要安装校验文件

1
npm i @types/react @types/react-dom --save

打开 tsconfig.json

1
2
3
# line10 将注释打开 改成
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"jsx": "react",

7.配置ts+vue环境

安装vue所需要的模块

1
npm install vue --save
1
npm install vue-loader  vue-template-compiler --save-dev

vue-template-compiler 是用来解析.vue文件中的 <template
vue-loader 是用来调取 vue-template-compiler的

使用vue-loader插件

1
2
const VueLoaderPlugin = require('vue-loader/lib/plugin');
new VueLoaderPlugin();

配置解析.vue文件

1
2
3
4
{
test:/\.vue$/,
use:'vue-loader'
}

新建vue-shims.d.ts(这个文件名后缀必须为 .d.ts),可以识别.vue文件,这样就可以引入 .vue的文件了。

1
2
3
4
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}

index.tsx文件

1
2
3
4
5
import Vue from 'vue';
import App from './App.vue'; // 这里就可以引入.vue后缀的文件了
let vm = new Vue({
render:h=>h(App)
}).$mount('#root')

vue里面如果使用ts的语法 如:

App.vue

1
2
3
4
5
6
<template>
<div>hello</div>
</template>
<script lang="ts">
// ******* 这里写ts语法
</script>

需要安装:

1
npm install vue-property-decorator --save-dev

配置.babelrc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"presets": [
["@babel/preset-env",{
"useBuiltIns":"usage",
"corejs":2
}],
"@babel/preset-react",
["@babel/preset-typescript",{
"allExtensions": true // **** 配置这里
}]
],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties",{"loose":true}],
"@babel/plugin-transform-runtime"
]
}

App.vue文件

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div>
<div v-for="(todo,index) in todos" :key="index">{{todo}}</div>
</div>
</template>
<script lang="ts">
import {Component,Vue} from 'vue-property-decorator';
@Component
export default class Todo extends Vue{
public todos = ['香蕉','苹果','橘子']
}
</script>

npm run dev
访问 http://localhost:3000/
可以看到页面上输出 香蕉, 苹果, 橘子

配置ts-loader

1
2
3
4
5
6
7
8
9
10
{
test: /\.tsx?/,
use: {
loader:'ts-loader',
options: {
appendTsSuffixTo: [/\.vue$/],
},
},
exclude: /node_modules/
}

8.配置代理

设置服务端接口

1
2
3
4
5
6
const express = require('express');
const app = express();
app.get('/api/list', (req, res) => {
res.send(['香蕉', '苹果', '橘子']);
});
app.listen(4000);

安装axios获取数据

1
npm install axios --save-dev

配置接口请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div>
<div v-for="(todo,index) in todos" :key="index">
{{todo}}
</div>
</div>
</template>

<script lang="ts">
import axios from 'axios';
import {Component ,Vue} from 'vue-property-decorator';
@Component
export default class Todo extends Vue{
public todos:string[] =[];
async mounted(){
let { data } = await axios.get('/api/list');
this.todos = data
}
}
</script>

配置服务器代理路由

1
2
3
4
5
proxy: {
'/api': {
target: 'http://localhost:4000',
},
}