手把手教你使用webpack打包前端组件(二)

539 阅读5分钟

这是我参与更文挑战的第8天,活动详情查看: 更文挑战

回顾前面

在上一篇文章中,我们讲述了什么是组件,与开发一个组件需要用到些什么工具,之后又带领着大家把原有的webpack-template的目录结构和配置文件进行了一些小改动,接下来这一期将把组件引进到模板中以及如何去调试配置我们的组件 ~ ~

如果还没有阅读第一篇的小伙伴们,请点击阅读手把手教你使用webpack打包前端组件(一)

导入插件

回到我们在上一篇文章中创建的src / components / better-draggable-ball / index.ts文件,把之前写好的插件代码粘贴入进去

images (1).jpg

// 插件代码
interface DefaultPositionType {
  x?: number,
  y?: number
}
interface Options {
  autoAdsorbent?: boolean;

  hideOffset?: number;

  defaultPosition?: DefaultPositionType;
}

export default class Drag {
  // 元素
  element: HTMLElement;

  // 屏幕尺寸
  screenWidth: number;

  screenHeight: number;

  // 元素大小
  elementWidth: number;

  elementHeight: number;

  isPhone: boolean;

  // 当前元素坐标
  elementX: number;

  elementY: number;

  // 元素offset
  elementOffsetX: number;

  elementOffsetY: number;

  // 是否处于拖动状态
  moving: boolean;

  // 吸附
  autoAdsorbent: boolean;

  // 隐藏
  hideOffset: number;

  constructor(element: HTMLElement, dConfig: Options = {}) {
    dConfig = this.InitParams(dConfig);
    this.element = element;
    this.screenWidth = window.innerWidth || window.outerWidth || 0;
    this.screenHeight = window.innerHeight || window.outerHeight || 0;
    this.elementWidth = this.element.offsetWidth || 0;
    this.elementHeight = this.element.offsetHeight || 0;
    this.isPhone = /(iPhone|iPad|iPod|iOS|Android)/i.test(navigator.userAgent);
    this.element.style.position = 'absolute';
    this.elementX = 0;
    this.elementY = 0;
    this.elementOffsetX = 0;
    this.elementOffsetY = 0;
    this.moving = false;
    this.autoAdsorbent = dConfig.autoAdsorbent;
    this.hideOffset = this.elementWidth * dConfig.hideOffset;
    if (!this.isPhone) {
      console.error('警告!!当前插件版本只兼容移动端');
    }
    // 默认位置
    this.setElementPosition(dConfig.defaultPosition.x, dConfig.defaultPosition.y);
    this.watchTouch();
  }

  protected InitParams(dConfig: Options):Options {
    // 处理下Options未配置的参数
    return {
      autoAdsorbent: dConfig.autoAdsorbent || false,
      hideOffset: dConfig.hideOffset || 0,
      defaultPosition: dConfig.defaultPosition || { x: 0, y: 0 },
    };
  }

  private watchTouch(): void {
    this.element.addEventListener('touchstart', (event: TouchEvent) => {
      const rect = (event.target as HTMLElement).getBoundingClientRect();
      // 页面被卷去的高度
      // 不兼容IE
      const docScrollTop = document.documentElement.scrollTop;
      this.elementOffsetX = event.targetTouches[0].pageX - rect.left;
      this.elementOffsetY = event.targetTouches[0].pageY - rect.top - docScrollTop;
      this.moving = true;
      this.element.addEventListener('touchmove', this.move.bind(this), { passive: false });
    });
    window.addEventListener('touchend', () => {
      this.moving = false;
      document.removeEventListener('touchmove', this.move);
      if (this.autoAdsorbent) this.adsorbent();
    });
  }

  private setElementPosition(x: number, y: number): void {
    // 溢出处理
    // 溢出范围
    // 但页面超出屏幕范围,计算当前屏幕范围
    const leftScope = this.moving ? 0 : 0 - this.hideOffset;
    // 当前屏幕right最大值
    const rs = this.screenWidth - this.elementWidth;
    const rightScope = this.moving ? rs : rs + this.hideOffset;
    const bottomScope = this.screenHeight - this.elementHeight;
    if (x <= leftScope && y <= 0) {
      [x, y] = [leftScope, 0];
    } else if (x >= rightScope && y <= 0) {
      [x, y] = [rightScope, 0];
    } else if (x <= leftScope && y >= bottomScope) {
      [x, y] = [leftScope, bottomScope];
    } else if (x >= rightScope && y >= bottomScope) {
      [x, y] = [rightScope, bottomScope];
    } else if (x > rightScope) {
      x = rightScope;
    } else if (y > bottomScope) {
      y = bottomScope;
    } else if (x <= leftScope) {
      x = leftScope;
    } else if (y <= 0) {
      y = 0;
    }
    this.elementX = x;
    this.elementY = y;
    this.element.style.top = `${y}px`;
    this.element.style.left = `${x}px`;
  }

  private move(event: TouchEvent): void {
    event.preventDefault();
    if (!this.moving) return;
    this.elementY = (event.touches[0].pageX - this.elementOffsetX);
    this.elementX = (event.touches[0].pageY - this.elementOffsetY);
    const ex = (event.touches[0].pageX - this.elementOffsetX);
    const ey = (event.touches[0].pageY - this.elementOffsetY);
    this.setElementPosition(ex, ey);
  }

  private animate(targetLeft: number, spd: number): void {
    const timer = setInterval(() => {
      let step = (targetLeft - this.elementX) / 10;
      // 对步长进行二次加工(大于0向上取整,小于0向下取整)
      step = step > 0 ? Math.ceil(step) : Math.floor(step);
      // 动画原理: 目标位置 = 当前位置 + 步长
      const x = this.elementX + step;
      this.setElementPosition(x, this.elementY);
      // 检测缓动动画有没有停止
      if (Math.abs(targetLeft - this.elementX) <= Math.abs(step)) {
        // 处理小数赋值
        const xt = targetLeft;
        this.setElementPosition(xt, this.elementY);
        clearInterval(timer);
      }
    }, spd);
  }

  private adsorbent():void {
    // 判断吸附方向
    // 屏幕中心点
    const screenCenterY = Math.round(this.screenWidth / 2);
    // left 最大值
    const rightScope = this.screenWidth - this.elementWidth;
    // 根据中心点来判断吸附方向
    if (this.elementX < screenCenterY) {
      this.animate(0 - (this.hideOffset), 10);
    } else {
      this.animate(rightScope + (this.hideOffset), 10);
    }
  }
}

开发调试

在组件开发的过程中往往少不了对组件的功能调试以及样式的展示,这时可以使用webpack-dev-server 这个插件,它给我们提供了一个基本的web server,并且具有实时重新更新页面的功能。

安装 webpack-dev-server

npm install --save-dev webpack-dev-server

!这里只是先安装,稍后我们将在配置文件中配置使用它

我们在pages文件夹下新建Drag的页面以及它的tsscss文件,用来调试组件:

7.png

Drag页面内容分别为:

// Drag.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Drag</title>
</head>
<body>
    <h2>
        Drag.html
    </h2>
</body>
</html>
// Drag.scss
*{
  margin: 0;
  padding: 0;
}
body{
  padding: 20px;
}
#drag{
  width: 50px;
  height: 50px;
  background-color: rgb(238, 238, 238);
  border-radius: 50%;
  border: 5px solid rgb(170, 170, 170);
}
p{
  height: 50px;
}
//Drag.ts
import './Drag.scss';
import Drag from '../../components/better-draggable-ball/index';

const dragDom = document.createElement('div');
dragDom.setAttribute('id', 'drag');
const body = document.getElementsByTagName('body')[0];
body.appendChild(dragDom);
new Drag(dragDom, {
  defaultPosition: { x: 10, y: 10 },
  autoAdsorbent: true,
});

把项目的根目录下的webpack.config.ts文件复制多一份出来,命名为webpack.config.dev.ts,这个文件只要用于调试时使用。

修改webpack.config.dev.ts文件:

在配置类型方面,我们需要做出一些修改,原本我们的配置对象模块中用的是webpack包中的config类型,但现在我们需要用到另外一个模块(webpack-dev-server)要在配置对象中配置devServer属性,而webpack中的config中没有devServer这个类型的属性,我们定义一个Configuration接口作为配置文件的类型,让它继承下webpack包中的config,当它底下有devServer的时候则对应上WebpackDevServerConfiguration

// webpack.config.dev.ts
import { Configuration as webpackConfiguration } from 'webpack';
import {Configuration as WebpackDevServerConfiguration} from 'webpack-dev-server';

interface Configuration extends webpackConfiguration{
  devServer ?: WebpackDevServerConfiguration;
}

加入devServer属性,配置运行目录和服务接口,这里compress指的是代码是否启用GZIP压缩:

const config: Configuration = {
	// 忽略一些代码
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 9000,
 	 },
  }

entryplugins属性中新增Drag页面

// webpack.config.dev.ts
entry: {
	// 忽略一些代码
    Drag: './src/pages/Drag/Drag.ts', // Drag页面
  },
// webpack.config.dev.ts
new HtmlWebpackPlugin({
      title: 'Drag',
      filename: 'Drag.html',
      template: './src/pages/Drag/Drag.html',
      chunks: ['Drag', 'main'],
    }),

修改package.json文件:

--config 参数是指定配置文件,如果没有指定默认是使用webpack.config.ts文件

--open 参数是当服务启动完毕后,自动的将目标URL打开

"scripts": {
    " ... 这里忽略了一些命令 ... "
    "serve": "webpack serve --config webpack.dev.config.ts --open"
  },

为了方便大家CVCVCV大法,我直接把整个webpack.config.dev.ts贴上来哈哈,懒是程序员第一生产力。

0165e6d206f64926b1a1653644856f8f.jpg

// webpack.config.dev.ts
import * as path from 'path';
import { Configuration as webpackConfiguration } from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import ESLintPlugin from 'eslint-webpack-plugin';
import {Configuration as WebpackDevServerConfiguration} from 'webpack-dev-server';
interface Configuration extends webpackConfiguration{
  devServer ?: WebpackDevServerConfiguration;
}
const config: Configuration = {
  mode: 'production',
  entry: {
    main: './src/main.ts',
    index: './src/pages/index/index.ts', // index页面
    Drag: './src/pages/Drag/Drag.ts', // hello页面
    'better-draggable-ball': './src/components/better-draggable-ball/index.ts', // better-draggable-ball 插件
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: (pathData:any) => (pathData.chunk.name === 'better-draggable-ball' ? 'js/components/[name]/[name].js' : 'js/[name]/[name].js'),
    clean: true,
  },
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 9000,
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        use: [
          // 将 JS 字符串生成为 style 节点
          'style-loader',
          // 将 CSS 转化成 CommonJS 模块
          'css-loader',
          // 将 Sass 编译成 CSS
          'sass-loader',
        ],
      },
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'index',
      filename: 'index.html',
      template: './src/pages/index/index.html',
      chunks: ['index', 'main'],
    }),
    new HtmlWebpackPlugin({
      title: 'Drag',
      filename: 'Drag.html',
      template: './src/pages/Drag/Drag.html',
      chunks: ['Drag', 'main'],
    }),
    new ESLintPlugin({
      extensions: ['js', 'ts'],
      exclude: '/node_modules/',
    }),

  ],
};

export default config;

执行npm run serve后,webserver将开始运行构建一个服务环境,对应的URL地址也在terminal中显示出来,webserver也会自动的帮我们打开浏览器访问对应的URL地址。

8.png

当这不是我们想要Drag页面,现在有两种方法可以切换到Drag页面中:

  • 在浏览器中把URL路径修改为http://localhost:8080/Drag.html

    • 不推荐这种做法,当你每次启动服务之后,你需要手动去修改它
  • devserver对象中,添加openPage属性,让其页面自动的显示出来

    • devServer: {
      	openPage: 'Drag.html',
      }
      

再跑一下,浏览器自动的打开Drag页面,我们的better-drag-ball组件也显示了出来。

9.gif

最后

到了这里,我们的组件的调试环境已经部署好了。

感谢大家的观看下一篇文章中我们将这个组件进行一个多版本的输出打包,让用户直接引用javascript文件就可以使用该组件。

😀😀 关注我,不迷路! 😀😀