初窥 webpack+vue+ts+jquery

508 阅读8分钟

事情的起源是这样子的:

  1. 小生在学习 ts -> 想在 ts 的学习过程中把 ts 引入在 html 里面
  2. 小生使用 webpack 配置了 ts 的操作 -> 又想把在 ts 中使用 jquery 操作 dom
  3. 小生配好了jquery -> 又觉得 vue 似乎也可以使用同样的方法引入 总结一下做了什么:

实现了一个 webpack 项目,并可以通过 webpack 打包模块化的 ts 模块和其他 es6 模块,然后又把 vue 和 jquery 集成在了 webpack 项目中。

PS:本文中大多数内容并没有采用比较官方的语言,而是我个人的理解,不喜勿喷,多多指教。

1. ts 的学习

1.1 为什么要学习 ts

这个问题贯穿了小生整个学习过程。为什么学习ts,ts有哪些优越性,ts的设计初衷是什么?

首先抄一下别人的说法:TypeScript 是 JavaScript 的一个超集,支持 ECMAScript 6 标准。TypeScript 设计目标是开发大型应用,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上。

1.1.1. 为什么学习他?

原因很单纯:别人都会我不会,岂不是显得我很呆?

1.1.2 ts有哪些优越性?

这个问题让我想起来刚开始看 vue 的时候,脑子里在想一个问题,vue 有什么用?jquery + html 不香嘛?为什么要再引入一个包?这样效率岂不是更低了 . . .

其实实际上确实是这样的,但是,要知道 vue 的初衷是开发单页面应用,模块化+工程化+自动化 . . . ,而 jquery + html 充其量是个 demo。一旦复杂的页面结构和复杂的业务逻辑上来,就会出现一个 js 脚本好几万行,再优秀的代码如果变成这样也会被人诟病:变量的声明在第3行,赋值在 33333 行,而且 我要加个变量,起个啥名字咧? new_param_for_feat_1011_baseUserInfo(这样总不会再重复了吧 !-_· ) ,所以vue就是在解决这些问题。

扯远了,回过头来说,与其说ts的优越性,不如说它实现了什么,它在做什么,它解决了什么问题?

第一点:TypeScript 是一种给 JavaScript 添加特性的语言扩展,支持 ES6语法。语言的扩展和 vue 、react、jquery 等框架还不一样,这些框架是以便于开发业务为目的而针对js的二次封装形成的一个 ,并进一步通过 webpack 等一系列手段形成了伴生这些框架而形成的新的文件规范,但归根结底他的语法还是 js 的基础语法。但是 ts 是针对 js 这门语言进行的扩展,什么意思嘞?如果把汉字和汉语拼音拆开当做两门语言,就相当于原来只有文字,而ts为文字加上了拼音这样的功能类似。

第二点:ts是的设计目标是开发大型应用,什么是大型应用?小生我没有概念,总体感觉呢,代码量应该很大,然后模块很多,业务很复杂,迭代很紧凑,参与的人很多 . . .

这样一来,源代码的可读性维护性就要求要很高,而且是很高很高,如果做到可读性高呢?加注释!而我认为 TS 最核心的地方就是 注释,并且把注释做到了极致,把注释添加到代码里面,并在编译阶段进行校验,以至于把99%的开发阶段的bug都扼杀在摇篮里。可读性高了维护性就高了呀! 具体案例会在下面的部分结合两个案例简介一下。

当然还有第三点,写起来可能是麻烦,但用起来是真香,尤其是结合vscode的提示,每个变量都变得规规矩矩的,而且一目了然。

1.2 记录几个demo

下面这几个demo,算是几个杂糅性比较强的几个小demo,很简单,便于理解。

1.2.1 结合基础类型,接口,类相关内容的一个简单实现

这个demo主要是为了记录一下基础语法,中间结合了枚举、接口、基础类型声明、接口的继承、类实现接口这些方面的使用。

// 枚举 sex
enum Sex { Girl = 0, Boy }

// 接口 person
interface Person {
  id: number
  name: string
  sex: Sex
  age?: number
}

// 函数接口 - join
// 注意:这样的接口只能在 class 中使用,不能被对象接收
interface Join {
  regist(): boolean
  login(id: number): Person
}

// 函数接口 - exit
interface Exit {
  logout(): boolean
}

// 函数接口 - user-operate - 继承
interface UserOperate extends Join, Exit {
  play(): string
}

// 类接口 
class UserPoc implements UserOperate, Person {
  id: number
  name: string
  sex: Sex
  age?: number = 16

  constructor(user: Person) {
    this.id = user.id
    this.name = user.name
    this.sex = user.sex
    this.age = user.age

    console.log('[PocPlus] init ...');
  }

  // 新的方法
  init() {
    this.regist()
    this.login(1)
    this.play()
    this.logout()
  }

  regist() {
    console.log('[PocPlus] regist ...');
    console.table({
      id: this.id,
      name: this.name,
      sex: this.sex,
      age: this.age || 18,
    })
    return true
  }

  login(id: number) {
    console.log('[PocPlus] login ...');
    return {
      id: this.id,
      name: this.name,
      sex: this.sex,
      age: this.age,
    }
  }

  play() {
    console.log('[PocPlus] playing ...');
    return this.name + ' is played.'
  }

  logout() {
    console.log('[PocPlus] logout ...');
    return true
  }
}

// 使用
let Tom = new UserPoc({ id: 1, name: 'Bob', sex: 1 })
Tom.init()

1.2.2 类型断言的用法

// 类型断言
  // 通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript 会假设你,程序员,已经进行了必须的检查。
  // 类型断言有两种形式。 其一是“尖括号”语法, 另一个为 as 语法
  /* 需求: 定义一个函数得到一个字符串或者数值数据的长度 */
  function getLen(param: number | string): number {
    if ((<string>param).length) {
      return (param as string).length
    } else {
      return XMLDocument.toString().length
    }
  }

1.2.3 函数类型的完整书写方式

// 函数类型
function add(x: number, y: number): number {
  return x + y
}

// 完整的函数类型书写方式
const myAdd: (x: number, y: number) => number = (x: number, y: number): number => {
  return x + y
}

const myAdd1: (x: number, y: number) => number = function (x: number, y: number): number {
  return x + y
}

1.2.4 泛型的两个尝试

/* 
  需求 1:
  声明一个方法,传入任何数据,导出传入值,且类型一致
*/
// 不使用泛型的方法
function idenFoo(arg: string): string {
  return arg
}
// ... 多个一样的方法,或者使用函数重载

// 使用any类型
function identFooAny(arg: any): any {
  return arg
}

// 使用泛型方法
function identFoo<T>(arg: T): T {
  return arg
}
// ---

/* 
  需求 2:
  泛型类型为非基础类型
  这个 demo 仅仅是因为是 demo 所以看起来有点恶心
*/

interface idenUserInfo<T> {
  data: T[]
  add: (t: T) => void
  getById: (id: number) => T | undefined  // getById 会返回 undefined
}

class idenUser {
  id?: number
  name: string
  age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}

class UserCURD implements idenUserInfo<idenUser>{
  data: idenUser[] = []

  add(user: idenUser): void {
    user = { ...user, id: Date.now() }
    this.data.push(user)
    console.log('保存user', user.id);
  }

  getById(id: number): idenUser | undefined {
    return this.data.find(item => item.id === id)
  }
}

const user1 = new UserCURD()
user1.add(new idenUser('Tom', 12))
user1.add(new idenUser('Bob', 16))
console.log(user1.data);

1.2.5 泛型约束

// 泛型约束
// 场景:如果我们直接怼一个泛型参数取 length 属性,会报错,因为这个泛型根本不知道它有这个属性
// 如:报错场景
function fn<T>(x: T): void {
  // console.log(x.length); // Error x 上不存在 length
}

// 引入约束来实现这个需求
interface LengthWise {
  length: number
}
function fn2<T extends LengthWise>(x: T): void {
  console.log(x.length);
}

// fn2(11)   // 报错
fn2([1, 2, 3])
fn('111111')

2. webpack + ts 进行项目打包

2.1 为什么要使用 webpack

ts需要编译以后才能被js引擎认识,所以要想验证你写的对不对,就必须要经过编译。而开发过程中最舒服的当然就是一边写一边检查了,而能实现这个需求的最常用的就是 webpack 了。

这里开始到最后都在使用webpack 代理着,但是只是在第一次进行了配置,并没有进行更改。

2.2 实现流程

实现流程主要分为几个部分:

  1. 新建并声明 webpack 入口文件: src/main.ts

    import './ts/tsdemo'
    
    (() => {
    
      // setTimeout(() => {
      //    document.open();
      //    document.write('hello webpack ts!')
      //    document.write('<br/>')
      //    document.close();
      // }, 500)
    
    })()
    
  2. 新建并声明 index 页面:public/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>webpack & TS</title>
  </head>
  <body>
    <div id="app">
      {{name}}
      <div class="main"></div>
      <div v-html="aTag"></div>
    </div>
  </body>
</html>
  1. webpack 配置文件 build/webpack.config.js

这里简述一下配置文件需要进行的一些配置吧:

1. 声明入口和导出文件 
2.  加载ts-loader解析ts文件 
3. 把打包的js文件写入html中 
4. 配置一个 node 服务 webpack-dev-server 
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')

const isProd = process.env.NODE_ENV === 'production' // 是否生产环境

function resolve(dir) {
  return path.resolve(__dirname, '..', dir)
}

module.exports = {
  mode: isProd ? 'production' : 'development',
  entry: {
    app: './src/main.ts'
  },

  output: {
    path: resolve('dist'),
    filename: '[name].[contenthash:8].js'
  },

  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        include: [resolve('src')]
      }
    ]
  },

  plugins: [
    new CleanWebpackPlugin({}),

    new HtmlWebpackPlugin({
      template: './public/index.html'
    })
  ],

  resolve: {
    extensions: ['.ts', '.tsx', '.js']
  },

  devtool: isProd ? 'cheap-module-source-map' : 'eval-cheap-source-map',

  devServer: {
    host: 'localhost', // 主机名
    stats: 'errors-only', // 打包日志输出输出错误信息
    port: 8081,
    open: true
  }
}
  1. 配置 webpack 相关命令:启动 和 打包 (dev/build)

    {
        "scripts"{
        	"dev": "cross-env NODE_ENV=development webpack serve --config build/webpack.config.js",
    		"build": "cross-env NODE_ENV=production webpack --config build/webpack.config.js"
    	}
    }
    
  2. 下载依赖

    # ts
    yarn add -D typescript
    # webpack
    yarn add -D webpack webpack-cli
    yarn add -D webpack-dev-server
    # html plugin
    yarn add -D html-webpack-plugin clean-webpack-plugin
    # ts loader
    yarn add -D ts-loader
    # cross-env 修改环境
    yarn add -D cross-env
    
  3. 执行

    # 启动
    npm run dev
    # 打包
    npm run build
    

2.3 阶段性总结

3. 加载 jquery 操作 dom

3.1 为什么选择 jquery?jquery 过气了吗?

jquery 到目前为止,可以作为最原始的一些针对于和原生html配合的一些封装的库。同类型的还有向 echarts、bootstrap之类的同类型的一些包。

与其说 jquery 过气了,不如说它完成了属于它的历史使命。在当今各种模块化开发的今天,jquery已经难以满足现在的开发节奏,所以现在用的较少了,但无论如何它都是一个非常优秀的js工具库。

3.2 实现流程

主流的实现流程分为两种:

第一种是在 index.html 中引入 jquery.min.js 文件,然后在webpack中通过 window 获取,这样去修改 index.html 而且还要附加下载 jquery 文件的方式固然不错,但是既然已经使用webpack了,当然希望所有的第三方包都通过模块化的思想音符进来。

第二种就是通过安装npm包,然后引入的方式,也是本文使用的方式:

  1. 安装使用的包

    npm install jquery --save
    
    npm install --save-dev @types/jquery typings
    
  2. 引入并使用

    为了验证 $ 符是否生效,这里修改了一下 index.html ,在里面加了个div。

    
    // webpack.config.js
    module: {
        rules: [
            //暴露$和jQuery到全局
            {
                test: require.resolve('jquery'), //require.resolve 用来获取模块的绝对路径
                use: [{
                    loader: 'expose-loader',
                    options: 'jQuery'
                }, {
                    loader: 'expose-loader',
                    options: '$'
                }]
            }
        ]
    }
    
    // 使用
    import $ from 'jquery'
    
    const a: string = '<a href="#">{{name}}</a>'
    $('.main').html(a)
    
    // index.html
    <body>
        <div id="app">
          <div class="main"></div>
        </div>
    </body>
    
    

4. 加载 vue 实现数据双向绑定

4.1 为什么要在这样的项目中挂载 vue?vue-cli不香吗?

没有为什么,就是为了熟悉一下操作,证明一下可以实现。

4.2 实现流程

  1. 下载 vue

    npm i vue -s
    
  2. 引入并使用

    1. 简单修改 html

      <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="UTF-8" />
          <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          <meta http-equiv="X-UA-Compatible" content="ie=edge" />
          <title>webpack & TS</title>
        </head>
        <body>
          <div id="app">
            {{name}}
          </div>
        </body>
      </html>
      
    2. 编辑 ts 文件

      import Vue from 'vue'
      
      const vm = new Vue({
        el: '#app',
        data: {
          name: 'Jhon Ranber',
        },
      })
      
      
    3. 修改webpack.config.js

      module: {
          rules: [
            {
              test: /\.tsx?$/,
              use: 'ts-loader',
              include: [resolve('src')]
            },
          ]
      },
      
    4. 验证

      重启服务,打开 localhost:8081,页面出现 Jhon Ranber。

5. 模块化实现 vue 的 methods,实现复用性

5.1 这是在做什么?

到这里是我真的想表达的一个思想,可能不实用,但是只是一个简单的解决方案,模块化 vue 的methods,实现methods 的可复用性。

场景是多个html及多个入口文件,每个html引入一个main.js。main.js 中使用 jquery 处理初始化的 html 加载,vue.js 实现数据双向绑定。

从而可以开发系列的工具函数,然后在多个页面中达到复用,进一步会使用webpack打包优化,将公共文件单独打包,优化打包体积,使首页展开比单页面应用从根本上变得更快,用户体验更优秀。

这个思路可能在现在微服务比较盛行的时代不太实用,但是可以用来活动活动脑子。

5.2 简单实现一个页面

  1. html

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>webpack & TS</title>
      </head>
      <body>
        <div id="app">
          {{name}}
          <div class="main"></div>
          <div v-html="aTag"></div>
        </div>
      </body>
    </html>
    
  2. ts

    // data-mtd.ts
    interface iFunc {
      [key: string]: (...arg: any) => any
    }
    
    let methodsList: iFunc = {
      click: () => { console.log('1'); },
      hover: () => { console.log('2'); },
      keyup: () => { console.log('3'); }
    }
    
    const mapMethods = (arr: Array<string>): object => {
      let obj: iFunc = {}
    
      arr.map(item => {
        obj[item] = methodsList[item]
      })
    
      return obj
    }
    export default mapMethods
    // ----
    
    // vue-00-main.ts
    import Vue from 'vue'
    import mapMethods from './data-mtd'
    
    const a: string = '<a href="#">{{name}}</a>'
    
    const vm = new Vue({
      el: '#app',
      data: {
        name: 'Jhon Ranber',
        aTag: a
      },
      mounted() {
        console.log('mounted::', this.name)
      },
      methods: mapMethods(['clickHandler', 'keyup'])
    })
    
    export default vm
    // -----
    
    // vue-01-引入.ts
    import $ from 'jquery'
    
    const a: string = '<a href="#">{{name}}</a>'
    $('.main').html(a)
    
    import _Vm from './vue-00-main'
    _Vm.$data.name = '诸葛-孔明'
    
    setTimeout(() => {
      _Vm.$data.name = '诸葛-村夫'
    }, 1000);
    // -----
    
    // main.ts
    import './vue/vue-01-引入'
    // -----
    
    

6. 收获与汇总

收获分两点:

  1. ts 这门”语言“有了初步的认知。
  2. 从以ts为基础编辑的项目中将ts单独摘出来,并进行简单的扩展。

学习地址:huaxhe.gitee.io/vue3_study_…